ruote 2.1.11 → 2.2.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 (217) hide show
  1. data/CHANGELOG.txt +60 -0
  2. data/CREDITS.txt +22 -4
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +6 -7
  5. data/Rakefile +58 -59
  6. data/TODO.txt +137 -65
  7. data/couch_url.txt +1 -0
  8. data/jruby_issue.txt +32 -0
  9. data/lib/ruote.rb +1 -1
  10. data/lib/ruote/context.rb +12 -10
  11. data/lib/ruote/engine.rb +280 -145
  12. data/lib/ruote/engine/process_error.rb +5 -5
  13. data/lib/ruote/engine/process_status.rb +47 -28
  14. data/lib/ruote/exp/command.rb +7 -10
  15. data/lib/ruote/exp/commanded.rb +2 -2
  16. data/lib/ruote/exp/condition.rb +130 -43
  17. data/lib/ruote/exp/fe_add_branches.rb +2 -2
  18. data/lib/ruote/exp/fe_apply.rb +1 -1
  19. data/lib/ruote/exp/fe_cancel_process.rb +3 -3
  20. data/lib/ruote/exp/fe_command.rb +3 -3
  21. data/lib/ruote/exp/fe_concurrence.rb +4 -4
  22. data/lib/ruote/exp/fe_concurrent_iterator.rb +17 -5
  23. data/lib/ruote/exp/fe_cron.rb +3 -3
  24. data/lib/ruote/exp/fe_cursor.rb +5 -5
  25. data/lib/ruote/exp/fe_define.rb +3 -3
  26. data/lib/ruote/exp/fe_echo.rb +3 -3
  27. data/lib/ruote/exp/fe_equals.rb +2 -2
  28. data/lib/ruote/exp/fe_error.rb +2 -2
  29. data/lib/ruote/exp/fe_filter.rb +519 -0
  30. data/lib/ruote/exp/fe_forget.rb +9 -2
  31. data/lib/ruote/exp/fe_given.rb +154 -0
  32. data/lib/ruote/exp/fe_if.rb +16 -13
  33. data/lib/ruote/exp/fe_inc.rb +3 -3
  34. data/lib/ruote/exp/fe_iterator.rb +4 -4
  35. data/lib/ruote/exp/fe_let.rb +75 -0
  36. data/lib/ruote/exp/fe_listen.rb +68 -12
  37. data/lib/ruote/exp/fe_lose.rb +110 -0
  38. data/lib/ruote/exp/fe_noop.rb +1 -1
  39. data/lib/ruote/exp/{fe_when.rb → fe_once.rb} +25 -21
  40. data/lib/ruote/exp/fe_participant.rb +14 -17
  41. data/lib/ruote/exp/fe_redo.rb +10 -6
  42. data/lib/ruote/exp/fe_ref.rb +1 -1
  43. data/lib/ruote/exp/fe_registerp.rb +112 -0
  44. data/lib/ruote/exp/fe_reserve.rb +3 -3
  45. data/lib/ruote/exp/fe_restore.rb +2 -2
  46. data/lib/ruote/exp/fe_save.rb +2 -2
  47. data/lib/ruote/exp/fe_sequence.rb +3 -4
  48. data/lib/ruote/exp/fe_set.rb +16 -7
  49. data/lib/ruote/exp/fe_subprocess.rb +23 -1
  50. data/lib/ruote/exp/fe_that.rb +92 -0
  51. data/lib/ruote/exp/fe_undo.rb +3 -3
  52. data/lib/ruote/exp/fe_unregisterp.rb +71 -0
  53. data/lib/ruote/exp/fe_wait.rb +2 -2
  54. data/lib/ruote/exp/flowexpression.rb +153 -78
  55. data/lib/ruote/exp/iterator.rb +2 -2
  56. data/lib/ruote/exp/merge.rb +2 -2
  57. data/lib/ruote/exp/ro_attributes.rb +14 -12
  58. data/lib/ruote/exp/ro_filters.rb +136 -0
  59. data/lib/ruote/exp/ro_persist.rb +51 -35
  60. data/lib/ruote/exp/ro_variables.rb +18 -27
  61. data/lib/ruote/fei.rb +73 -33
  62. data/lib/ruote/id/mnemo_wfid_generator.rb +1 -1
  63. data/lib/ruote/id/wfid_generator.rb +11 -4
  64. data/lib/ruote/log/default_history.rb +122 -0
  65. data/lib/ruote/log/pretty.rb +36 -8
  66. data/lib/ruote/log/storage_history.rb +37 -5
  67. data/lib/ruote/log/test_logger.rb +26 -24
  68. data/lib/ruote/log/wait_logger.rb +5 -3
  69. data/lib/ruote/part/block_participant.rb +22 -11
  70. data/lib/ruote/part/engine_participant.rb +6 -7
  71. data/lib/ruote/part/local_participant.rb +6 -12
  72. data/lib/ruote/part/no_op_participant.rb +4 -4
  73. data/lib/ruote/part/null_participant.rb +4 -4
  74. data/lib/ruote/part/smtp_participant.rb +4 -4
  75. data/lib/ruote/part/storage_participant.rb +40 -20
  76. data/lib/ruote/part/template.rb +4 -4
  77. data/lib/ruote/participant.rb +0 -1
  78. data/lib/ruote/{parser.rb → reader.rb} +30 -25
  79. data/lib/ruote/{parser → reader}/ruby_dsl.rb +28 -11
  80. data/lib/ruote/{parser → reader}/xml.rb +6 -5
  81. data/lib/ruote/receiver/base.rb +35 -13
  82. data/lib/ruote/storage/base.rb +20 -18
  83. data/lib/ruote/storage/composite_storage.rb +10 -10
  84. data/lib/ruote/storage/fs_storage.rb +17 -10
  85. data/lib/ruote/storage/hash_storage.rb +29 -18
  86. data/lib/ruote/svc/dispatch_pool.rb +41 -14
  87. data/lib/ruote/svc/dollar_sub.rb +50 -17
  88. data/lib/ruote/svc/error_handler.rb +19 -11
  89. data/lib/ruote/svc/expression_map.rb +4 -4
  90. data/lib/ruote/svc/participant_list.rb +105 -100
  91. data/lib/ruote/svc/tracker.rb +58 -18
  92. data/lib/ruote/svc/treechecker.rb +51 -24
  93. data/lib/ruote/tree_dot.rb +4 -4
  94. data/lib/ruote/util/filter.rb +440 -0
  95. data/lib/ruote/util/hashdot.rb +4 -4
  96. data/lib/ruote/util/look.rb +2 -6
  97. data/lib/ruote/util/lookup.rb +9 -7
  98. data/lib/ruote/util/misc.rb +40 -8
  99. data/lib/ruote/util/ometa.rb +1 -1
  100. data/lib/ruote/util/serializer.rb +4 -4
  101. data/lib/ruote/util/subprocess.rb +29 -9
  102. data/lib/ruote/util/time.rb +4 -4
  103. data/lib/ruote/util/tree.rb +3 -3
  104. data/lib/ruote/version.rb +2 -2
  105. data/lib/ruote/worker.rb +55 -32
  106. data/lib/ruote/workitem.rb +64 -11
  107. data/ruote.gemspec +31 -302
  108. data/test/bm/launch_bench.rb +37 -0
  109. data/test/functional/base.rb +60 -18
  110. data/test/functional/concurrent_base.rb +2 -2
  111. data/test/functional/ct_0_concurrence.rb +1 -1
  112. data/test/functional/ct_1_iterator.rb +1 -1
  113. data/test/functional/ct_2_cancel.rb +1 -1
  114. data/test/functional/eft_0_process_definition.rb +2 -2
  115. data/test/functional/eft_10_cancel_process.rb +1 -1
  116. data/test/functional/eft_11_wait.rb +19 -11
  117. data/test/functional/eft_12_listen.rb +79 -13
  118. data/test/functional/eft_13_iterator.rb +13 -10
  119. data/test/functional/eft_14_cursor.rb +98 -9
  120. data/test/functional/eft_15_loop.rb +6 -4
  121. data/test/functional/eft_16_if.rb +12 -0
  122. data/test/functional/eft_18_concurrent_iterator.rb +31 -32
  123. data/test/functional/eft_19_reserve.rb +4 -4
  124. data/test/functional/eft_1_echo.rb +9 -0
  125. data/test/functional/eft_20_save.rb +4 -4
  126. data/test/functional/{eft_28_when.rb → eft_28_once.rb} +33 -7
  127. data/test/functional/eft_30_ref.rb +17 -2
  128. data/test/functional/eft_31_registerp.rb +130 -0
  129. data/test/functional/eft_32_lose.rb +93 -0
  130. data/test/functional/eft_33_let.rb +31 -0
  131. data/test/functional/eft_34_given.rb +123 -0
  132. data/test/functional/eft_35_filter.rb +269 -0
  133. data/test/functional/eft_3_participant.rb +4 -6
  134. data/test/functional/eft_4_set.rb +16 -2
  135. data/test/functional/eft_5_subprocess.rb +2 -4
  136. data/test/functional/eft_6_concurrence.rb +29 -29
  137. data/test/functional/eft_8_undo.rb +39 -3
  138. data/test/functional/eft_9_redo.rb +94 -2
  139. data/test/functional/ft_10_dollar.rb +81 -2
  140. data/test/functional/ft_11_recursion.rb +13 -17
  141. data/test/functional/ft_12_launchitem.rb +9 -5
  142. data/test/functional/ft_13_variables.rb +7 -9
  143. data/test/functional/ft_14_re_apply.rb +6 -9
  144. data/test/functional/ft_15_timeout.rb +18 -18
  145. data/test/functional/ft_16_participant_params.rb +1 -3
  146. data/test/functional/ft_17_conditional.rb +25 -2
  147. data/test/functional/ft_18_kill.rb +65 -12
  148. data/test/functional/ft_1_process_status.rb +147 -71
  149. data/test/functional/ft_20_storage_participant.rb +0 -1
  150. data/test/functional/ft_21_forget.rb +82 -1
  151. data/test/functional/{ft_24_block_participants.rb → ft_24_block_participant.rb} +42 -11
  152. data/test/functional/ft_25_receiver.rb +47 -17
  153. data/test/functional/{ft_26_participant_timeout.rb → ft_26_participant_rtimeout.rb} +56 -19
  154. data/test/functional/ft_29_part_template.rb +6 -5
  155. data/test/functional/ft_2_errors.rb +21 -37
  156. data/test/functional/ft_30_smtp_participant.rb +1 -1
  157. data/test/functional/ft_31_part_blocking.rb +8 -6
  158. data/test/functional/ft_34_cursor_rewind.rb +13 -10
  159. data/test/functional/ft_35_add_service.rb +1 -1
  160. data/test/functional/ft_36_storage_history.rb +24 -1
  161. data/test/functional/ft_37_default_history.rb +109 -0
  162. data/test/functional/ft_38_participant_more.rb +10 -10
  163. data/test/functional/ft_39_wait_for.rb +12 -9
  164. data/test/functional/ft_3_participant_registration.rb +111 -32
  165. data/test/functional/ft_40_wait_logger.rb +2 -1
  166. data/test/functional/ft_41_participants.rb +30 -4
  167. data/test/functional/ft_43_participant_on_reply.rb +6 -23
  168. data/test/functional/ft_45_participant_accept.rb +4 -4
  169. data/test/functional/ft_46_launch_single.rb +36 -2
  170. data/test/functional/ft_47_wfid_generator.rb +54 -0
  171. data/test/functional/ft_48_lose.rb +112 -0
  172. data/test/functional/ft_49_engine_on_error.rb +201 -0
  173. data/test/functional/ft_4_cancel.rb +66 -6
  174. data/test/functional/ft_50_engine_config.rb +22 -0
  175. data/test/functional/ft_51_misc.rb +67 -0
  176. data/test/functional/ft_52_case.rb +134 -0
  177. data/test/functional/ft_53_engine_on_terminate.rb +95 -0
  178. data/test/functional/ft_54_patterns.rb +104 -0
  179. data/test/functional/{ft_37_engine_participant.rb → ft_55_engine_participant.rb} +4 -5
  180. data/test/functional/ft_56_filter_attribute.rb +259 -0
  181. data/test/functional/ft_5_on_error.rb +77 -30
  182. data/test/functional/ft_6_on_cancel.rb +66 -11
  183. data/test/functional/ft_7_tags.rb +94 -5
  184. data/test/functional/ft_8_participant_consumption.rb +36 -5
  185. data/test/functional/ft_9_subprocesses.rb +10 -10
  186. data/test/functional/rt_1_listen.rb +3 -3
  187. data/test/functional/{rt_3_when.rb → rt_3_once.rb} +4 -4
  188. data/test/functional/storage_helper.rb +15 -13
  189. data/test/functional/test.rb +1 -3
  190. data/test/test_helper.rb +0 -8
  191. data/test/unit/storage.rb +154 -10
  192. data/test/unit/{ut_0_ruby_parser.rb → ut_0_ruby_reader.rb} +61 -11
  193. data/test/unit/ut_11_lookup.rb +7 -0
  194. data/test/unit/ut_13_serializer.rb +1 -1
  195. data/test/unit/ut_15_util.rb +23 -0
  196. data/test/unit/{ut_16_parser.rb → ut_16_reader.rb} +11 -13
  197. data/test/unit/ut_1_fei.rb +57 -10
  198. data/test/unit/ut_20_composite_storage.rb +25 -11
  199. data/test/unit/ut_21_participant_list.rb +47 -0
  200. data/test/unit/ut_22_filter.rb +903 -0
  201. data/test/unit/ut_3_wait_logger.rb +2 -6
  202. data/test/unit/ut_6_condition.rb +164 -17
  203. data/test/unit/ut_7_workitem.rb +28 -0
  204. data/test/unit/ut_8_tree_to_dot.rb +1 -1
  205. data/test/unit/{ut_9_xml_parser.rb → ut_9_xml_reader.rb} +5 -5
  206. metadata +108 -84
  207. data/.gitignore +0 -4
  208. data/examples/barley.rb +0 -391
  209. data/examples/flickr_report.rb +0 -107
  210. data/examples/pong.rb +0 -37
  211. data/examples/ruote_quickstart.rb +0 -43
  212. data/examples/web_first_page.rb +0 -68
  213. data/lib/ruote/part/hash_participant.rb +0 -91
  214. data/test/README.rdoc +0 -15
  215. data/test/functional/crunner.sh +0 -19
  216. data/test/pdef.xml +0 -7
  217. data/test/unit/ut_2_wfidgen.rb +0 -21
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -101,7 +101,7 @@ module Ruote::Exp
101
101
  reply_to_parent(h.applied_workitem)
102
102
  end
103
103
 
104
- def reply (workitem)
104
+ def reply(workitem)
105
105
 
106
106
  # never called
107
107
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -51,14 +51,14 @@ module Ruote::Exp
51
51
  #
52
52
  class CancelProcessExpression < FlowExpression
53
53
 
54
- names :cancel_process
54
+ names :cancel_process, :terminate
55
55
 
56
56
  def apply
57
57
 
58
58
  @context.storage.put_msg('cancel_process', 'wfid' => h.fei['wfid'])
59
59
  end
60
60
 
61
- def reply (workitem)
61
+ def reply(workitem)
62
62
 
63
63
  # never called
64
64
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -87,7 +87,7 @@ module Ruote::Exp
87
87
 
88
88
  include CommandMixin
89
89
 
90
- names :skip, :back, :jump, :rewind, :continue, :break, :stop
90
+ names :skip, :back, :jump, :rewind, :continue, :break, :stop, :over
91
91
 
92
92
  def apply
93
93
 
@@ -143,7 +143,7 @@ module Ruote::Exp
143
143
  # (CommandExpression includes CommandMixin, but since it doesn't have
144
144
  # children, no need to 'evince' it)
145
145
  #
146
- def fetch_command_target (exp=parent)
146
+ def fetch_command_target(exp=parent)
147
147
 
148
148
  case exp
149
149
  when nil then nil
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -182,7 +182,7 @@ module Ruote::Exp
182
182
  apply_children
183
183
  end
184
184
 
185
- def reply (workitem)
185
+ def reply(workitem)
186
186
 
187
187
  if h.cmerge == 'first' || h.cmerge == 'last'
188
188
  h.workitems << workitem
@@ -230,7 +230,7 @@ module Ruote::Exp
230
230
  msgs.each { |m| @context.storage.put_msg('apply', m) }
231
231
  end
232
232
 
233
- def over? (workitem)
233
+ def over?(workitem)
234
234
 
235
235
  over_if = attribute(:over_if, workitem)
236
236
  over_unless = attribute(:over_unless, workitem)
@@ -253,7 +253,7 @@ module Ruote::Exp
253
253
  h.ccount ? [ h.ccount, tree_children.size ].min : tree_children.size
254
254
  end
255
255
 
256
- def reply_to_parent (_workitem)
256
+ def reply_to_parent(_workitem)
257
257
 
258
258
  workitem = merge_all_workitems
259
259
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -112,23 +112,34 @@ module Ruote::Exp
112
112
  #
113
113
  # Read more at the 'add_branches' expression description.
114
114
  #
115
+ #
116
+ # == 'citerator'
117
+ #
118
+ # 'citerator' is an alias for 'concurrent_iterator'.
119
+ #
120
+ # pdef = Ruote.process_definition :name => 'test' do
121
+ # citerator :on_val => 'alice, bob, charly', :to_var => 'v' do
122
+ # participant '${v:v}'
123
+ # end
124
+ # end
125
+ #
115
126
  class ConcurrentIteratorExpression < ConcurrenceExpression
116
127
 
117
128
  include IteratorMixin
118
129
 
119
- names :concurrent_iterator
130
+ names :concurrent_iterator, :citerator
120
131
 
121
132
  ADD_BRANCHES_FIELD = '__add_branches__'
122
133
 
123
134
  # Overrides FlowExpression#register_child to make sure that persist is
124
135
  # not called.
125
136
  #
126
- def register_child (fei)
137
+ def register_child(fei)
127
138
 
128
139
  h.children << fei
129
140
  end
130
141
 
131
- def add_branches (list)
142
+ def add_branches(list)
132
143
 
133
144
  if h.times_iterator && list.size == 1
134
145
 
@@ -142,6 +153,7 @@ module Ruote::Exp
142
153
  h.list_size += 1
143
154
 
144
155
  workitem = Ruote.fulldup(h.applied_workitem)
156
+ #workitem = Rufus::Json.dup(h.applied_workitem)
145
157
 
146
158
  variables = { 'ii' => h.list_size - 1 }
147
159
 
@@ -159,7 +171,7 @@ module Ruote::Exp
159
171
  end
160
172
  end
161
173
 
162
- def reply (workitem)
174
+ def reply(workitem)
163
175
 
164
176
  if ab = workitem['fields'].delete(ADD_BRANCHES_FIELD)
165
177
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -99,7 +99,7 @@ module Ruote::Exp
99
99
  reschedule
100
100
  end
101
101
 
102
- def reply (workitem)
102
+ def reply(workitem)
103
103
 
104
104
  launch_sub(
105
105
  "#{h.fei['expid']}_0",
@@ -110,7 +110,7 @@ module Ruote::Exp
110
110
  reschedule
111
111
  end
112
112
 
113
- def cancel (flavour)
113
+ def cancel(flavour)
114
114
 
115
115
  @context.storage.delete_schedule(h.job_id)
116
116
  reply_to_parent(h.applied_workitem)
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -178,7 +178,7 @@ module Ruote::Exp
178
178
  # class Reviewer
179
179
  # include Ruote::LocalParticipant
180
180
  #
181
- # def consume (workitem)
181
+ # def consume(workitem)
182
182
  # # somehow review the book
183
183
  # if review == 'bad'
184
184
  # #workitem.fields['__command__'] = [ 'rewind' ] # old style
@@ -189,7 +189,7 @@ module Ruote::Exp
189
189
  # reply_to_engine(workitem)
190
190
  # end
191
191
  #
192
- # def cancel (fei, flavour)
192
+ # def cancel(fei, flavour)
193
193
  # # cancel if review is still going on...
194
194
  # end
195
195
  # end
@@ -250,7 +250,7 @@ module Ruote::Exp
250
250
 
251
251
  # Determines which child expression of the cursor is to be applied next.
252
252
  #
253
- def move_on (workitem=h.applied_workitem)
253
+ def move_on(workitem=h.applied_workitem)
254
254
 
255
255
  position = workitem['fei'] == h.fei ?
256
256
  -1 : Ruote::FlowExpressionId.child_id(workitem['fei'])
@@ -286,7 +286,7 @@ module Ruote::Exp
286
286
  # Jumps to an integer position, or the name of an expression
287
287
  # or a tag name of a ref name.
288
288
  #
289
- def jump_to (workitem, position, arg)
289
+ def jump_to(workitem, position, arg)
290
290
 
291
291
  pos = Integer(arg) rescue nil
292
292
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -92,7 +92,7 @@ module Ruote::Exp
92
92
  # Returns true if the tree's root expression is a definition
93
93
  # (define, process_definition, ...)
94
94
  #
95
- def self.is_definition? (tree)
95
+ def self.is_definition?(tree)
96
96
 
97
97
  self.expression_names.include?(tree.first)
98
98
  end
@@ -100,7 +100,7 @@ module Ruote::Exp
100
100
  # Used by instances of this class and also the expression pool,
101
101
  # when launching a new process instance.
102
102
  #
103
- def self.reorganize (tree)
103
+ def self.reorganize(tree)
104
104
 
105
105
  definitions, bodies = tree[2].partition { |b| is_definition?(b) }
106
106
  name = tree[1]['name'] || tree[1].keys.find { |k| tree[1][k] == nil }
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -40,7 +40,7 @@ module Ruote::Exp
40
40
 
41
41
  def apply
42
42
 
43
- text = "#{attribute_text}\n"
43
+ text = "#{attribute(:text) || attribute_text}\n"
44
44
 
45
45
  if t = @context['s_tracer']
46
46
  t << text
@@ -51,7 +51,7 @@ module Ruote::Exp
51
51
  reply_to_parent(h.applied_workitem)
52
52
  end
53
53
 
54
- def reply (workitem)
54
+ def reply(workitem)
55
55
 
56
56
  # never called
57
57
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -96,7 +96,7 @@ module Ruote::Exp
96
96
  keys.collect { |k| grab_value(k) }
97
97
  end
98
98
 
99
- def grab_value (k)
99
+ def grab_value(k)
100
100
 
101
101
  attval = attribute(k)
102
102
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2010, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2011, 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
@@ -64,7 +64,7 @@ module Ruote::Exp
64
64
  'workitem' => h.applied_workitem)
65
65
  end
66
66
 
67
- def reply (workitem)
67
+ def reply(workitem)
68
68
 
69
69
  return reply_to_parent(workitem) if h.triggered
70
70
 
@@ -0,0 +1,519 @@
1
+ #--
2
+ # Copyright (c) 2005-2011, 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
+ require 'ruote/util/filter'
26
+
27
+
28
+ module Ruote::Exp
29
+
30
+ #
31
+ # Filter is a one-way filter expression. It filters workitem fields.
32
+ # Validations and Transformations are possible.
33
+ #
34
+ # Validations will raise errors (that'll block the process segment
35
+ # unless an :on_error attribute somehow deals with the problem).
36
+ #
37
+ # Transformations will copy values around fields.
38
+ #
39
+ # There are two ways to use it. With a single rule or with an array of
40
+ # rules.
41
+ #
42
+ # filter 'x', :type => 'string'
43
+ # # will raise an error if the field 'x' doesn't contain a String
44
+ #
45
+ # or
46
+ #
47
+ # filter :in => [
48
+ # { :field => 'x', :type => 'string' },
49
+ # { :field => 'y', :type => 'number' }
50
+ # ]
51
+ #
52
+ # For the remainder of this piece of documentation, the one rule filter
53
+ # will be used.
54
+ #
55
+ # == filtering targets (field names)
56
+ #
57
+ # Top level field names are OK :
58
+ #
59
+ # filter 'customer_id', :type => 'string'
60
+ # filter 'invoice_id', :type => 'number'
61
+ #
62
+ # Pointing to fields lying deeper is OK :
63
+ #
64
+ # filter 'customer.id', :type => 'number'
65
+ # filter 'customer.name', :type => 'string'
66
+ # filter 'invoice', :type => 'array'
67
+ # filter 'invoice.0.id', :type => 'number'
68
+ #
69
+ # (Note the dollar notation is also OK with such dotted identifiers)
70
+ #
71
+ # It's possible to target multiple fields by passing a list of field names
72
+ # or a regular expression.
73
+ #
74
+ # filter 'city, region, country', :type => 'string'
75
+ # # will make sure that those 3 fields hold a string value
76
+ #
77
+ # filter '/^address\.x_/', :type => number
78
+ # filter '/^address!x_/', :type => number
79
+ # # fields whosename start with x_ in the address hash should be numbers
80
+ #
81
+ # Note the "!" used as a shortcut for "\." in the second line.
82
+ #
83
+ #
84
+ # == validations
85
+ #
86
+ # === 'type'
87
+ #
88
+ # Ruote is a Ruby library, it adopts Ruby "laissez-faire" for workitem
89
+ # fields, but sometimes, some type oriented validation is necessary.
90
+ # Ruote limits itself to the types found in the JSON specification with
91
+ # one or two additions.
92
+ #
93
+ # filter 'x', :type => 'string'
94
+ # filter 'x', :type => 'number'
95
+ # filter 'x', :type => 'bool'
96
+ # filter 'x', :type => 'boolean'
97
+ # filter 'x', :type => 'null'
98
+ #
99
+ # filter 'x', :type => 'array'
100
+ #
101
+ # filter 'x', :type => 'object'
102
+ # filter 'x', :type => 'hash'
103
+ # # 'object' and 'hash' are equivalent
104
+ #
105
+ # It's OK to pass multiple types for a field
106
+ #
107
+ # filter 'x', :type => 'bool,number'
108
+ # filter 'x', :type => [ 'string', 'array' ]
109
+ #
110
+ # filter 'x', :type => 'string,null'
111
+ # # a string or null or not set
112
+ #
113
+ # The array and the object/hash types accept a subtype for their values
114
+ # (a hash/object must have string keys anyway).
115
+ #
116
+ # filter 'x', :type => 'array<number>'
117
+ # filter 'x', :type => 'array<string>'
118
+ # filter 'x', :type => 'array<array<string>>'
119
+ #
120
+ # filter 'x', :type => 'array<string,number>'
121
+ # # an array of strings or numbers (both)
122
+ # filter 'x', :type => 'array<string>,array<number>'
123
+ # # an array of strings or an array of numbers
124
+ #
125
+ # === 'match' and 'smatch'
126
+ #
127
+ # 'match' will check if a field, when turned into a string, matches
128
+ # a given regular expression.
129
+ #
130
+ # filter 'x', :match => '1'
131
+ # # will match "11", 1, 1.0, "212"
132
+ #
133
+ # 'smatch' works the same but only accepts fields that are strings.
134
+ #
135
+ # filter 'x', :smatch => '^user_'
136
+ # # valid only if x's value is a string that starts with "user_"
137
+ #
138
+ # === 'size' and 'empty'
139
+ #
140
+ # 'size' is valid for values that respond to the #size method (strings
141
+ # hashes and arrays).
142
+ #
143
+ # filter 'x', :size => 4
144
+ # # will be valid of values like [ 1, 2, 3, 4 ], "toto" or
145
+ # # { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }
146
+ #
147
+ # filter 'x', :size => [ 4, 5 ]
148
+ # filter 'x', :size => '4,5'
149
+ # # four to five elements
150
+ #
151
+ # filter 'x', :size => [ 4 ]
152
+ # filter 'x', :size => [ 4, nil ]
153
+ # filter 'x', :size => '4,'
154
+ # # four or more elements
155
+ #
156
+ # filter 'x', :size => [ nil, 4 ]
157
+ # filter 'x', :size => ',4'
158
+ # # four elements or less
159
+ #
160
+ # Similarly, the 'empty' check will evaluate to true (ie not raise an
161
+ # exception) if the value responds to #empty? and is, well, not empty.
162
+ #
163
+ # filter 'x', :empty => true
164
+ #
165
+ # === 'in'
166
+ #
167
+ # Checks if a value is in a given set of values.
168
+ #
169
+ # filter 'x', :in => [ 1, 2, 3 ]
170
+ # filter 'x', :in => "john, jeff, jim"
171
+ #
172
+ # === 'has'
173
+ #
174
+ # Checks if an array contains certain values
175
+ #
176
+ # filter 'x', :has => 1
177
+ # filter 'x', :has => "x"
178
+ # filter 'x', :has => [ 1, 7, 12 ]
179
+ # filter 'x', :has => "abraham, bob, charly"
180
+ #
181
+ # Also checks if a hash has certain keys (strings only of course)
182
+ #
183
+ # filter 'x', :has => "x"
184
+ # filter 'x', :has => "abraham, bob, charly"
185
+ #
186
+ # === 'valid'
187
+ #
188
+ # Sometimes, it's better to immediately say 'true' or 'false'.
189
+ #
190
+ # filter 'x', :valid => 'true'
191
+ # filter 'x', :valid => 'false'
192
+ #
193
+ # Not very useful...
194
+ #
195
+ # In fact, it's meant to be used with the dollar notation
196
+ #
197
+ # filter 'x', :valid => '${other.field}'
198
+ # # will be valid if ${other.field} evaluates to 'true'...
199
+ #
200
+ # === cumulating validations
201
+ #
202
+ # As seen before, type validations can be cumulated.
203
+ #
204
+ # filter 'x', :type => 'bool,number'
205
+ #
206
+ # Validations can be cumulated as well
207
+ #
208
+ # filter 'x', :type => 'array<number>', :has => [ 1, 2 ]
209
+ # # will be valid if the field 'x' holds an array of numbers
210
+ # # and that array has 1 and 2 among its elements
211
+ #
212
+ # === validation errors
213
+ #
214
+ # By defaults a validation error will result in a process error (ie the
215
+ # process instance will have to be manually fixed and resumed, or there
216
+ # is a :on_error somewhere dealing automatically with errors).
217
+ #
218
+ # It's possible to prevent raising an error and simply record the validation
219
+ # errors.
220
+ #
221
+ # filter 'x', :type => 'bool,number', :record => true
222
+ #
223
+ # will enumerate validation errors in the '__validation_errors__' workitem
224
+ # field.
225
+ #
226
+ # filter 'y', :type => 'bool,number', :record => 'verrors'
227
+ #
228
+ # will enumerate validation errors in teh 'verrors' workitem field.
229
+ #
230
+ # To flush the recording field, use :flush => true
231
+ #
232
+ # sequence do
233
+ # filter 'x', :type => 'string', :record => true
234
+ # filter 'y', :type => 'number', :record => true, :flush => true
235
+ # participant 'after'
236
+ # end
237
+ #
238
+ # the participant 'after' will only see the result of the second filter.
239
+ #
240
+ # For complex filters, if the first rule has :record => true, the
241
+ # 'recording' will happen for the whole filter.
242
+ #
243
+ # sequence do
244
+ # filter :in => [
245
+ # { :field => 'x', :type => 'string', :record => true },
246
+ # { :field => 'y', :type => 'number' } ]
247
+ # participant 'after'
248
+ # end
249
+ #
250
+ #
251
+ # == transformations
252
+ #
253
+ # So far, only the validation aspect of filter was shown. They can also be
254
+ # used to transform the workitem.
255
+ #
256
+ # filter 'x', :type => 'string', :or => 'missing'
257
+ # # will replace the value of x by 'missing' if it's not a string
258
+ #
259
+ # filter 'z', :remove => true
260
+ # # will remove the workitem field z
261
+ #
262
+ # filter 'a,b,c', 'set' => '---'
263
+ # # sets the field a, b and c to '---'
264
+ #
265
+ # === 'remove'
266
+ #
267
+ # Removes a field (or a subfield).
268
+ #
269
+ # filter 'z', :remove => true
270
+ #
271
+ # === 'default'
272
+ #
273
+ # If there is no value for a field, sets it
274
+ #
275
+ # filter 'x', 'default' => 0
276
+ # # will set x to 0, if it's not set or its value is nil
277
+ #
278
+ # filter '/^user-.+/', 'default' => 'nemo'
279
+ # # will set any 'user-...' field to 'nemo' if its value is nil
280
+ #
281
+ # === 'or'
282
+ #
283
+ # 'or' combines with a condition. The 'or' value is set if the condition
284
+ # evaluates to false.
285
+ #
286
+ # Using 'or' without a condition makes it equivalent to a 'default'.
287
+ #
288
+ # filter 'x', 'or' => 0
289
+ # # will set x to 0, if it's not set or its value is nil
290
+ #
291
+ # filter 'x', 'type' => 'number', 'or' => 0
292
+ # # if x is not set or is not a number, will set it to 0
293
+ #
294
+ # Multiple conditions are OK
295
+ #
296
+ # filter 'x', 't' => 'array', 'has' => 'cat', 'or' => []
297
+ # # if x is an array and has the 'cat' element, nothing will happen.
298
+ # # Else x will be set to [].
299
+ #
300
+ # === 'and'
301
+ #
302
+ # 'and' is much like 'or', but it triggers if the condition evaluates to true.
303
+ #
304
+ # filter 'x', 'type' => number, 'and' => '*removed*'
305
+ # # if x is a number, it will replace it with '*removed*'
306
+ #
307
+ # === 'set'
308
+ #
309
+ # Like 'remove' removes unconditionally, 'set' sets a field unconditionally.
310
+ #
311
+ # filter 'x', 'set' => 'blue'
312
+ # # sets the field x to 'blue'
313
+ #
314
+ # === copy, merge, migrate / to, from
315
+ #
316
+ # # in : { 'x' => 'y' }
317
+ # filter 'x', 'copy_to' => 'z'
318
+ # # out : { 'x' => 'y', 'z' => 'y' }
319
+ #
320
+ # # in : { 'x' => 'y' }
321
+ # filter 'z', 'copy_from' => 'x'
322
+ # # out : { 'x' => 'y', 'z' => 'y' }
323
+ #
324
+ # # in : { 'x' => 'y' }
325
+ # filter 'z', 'copy_from' => 'x'
326
+ # # out : { 'x' => 'y', 'z' => 'y' }
327
+ #
328
+ # # in : { 'a' => %w[ x y ]})
329
+ # filter '/a\.(.+)/', 'copy_to' => 'b\1'
330
+ # # out : { 'a' => %w[ x y ], 'b0' => 'x', 'b1' => 'y' },
331
+ #
332
+ # # in : { 'a' => %w[ x y ]})
333
+ # filter '/a!(.+)/', 'copy_to' => 'b\1'
334
+ # # out : { 'a' => %w[ x y ], 'b0' => 'x', 'b1' => 'y' },
335
+ # #
336
+ # # '!' is used as a replacement for '\.' in regexes
337
+ #
338
+ # # in : { 'a' => 'b', 'c' => 'd', 'source' => [ 7 ] })
339
+ # filter '/^.$/', 'copy_from' => 'source.0'
340
+ # # out : { 'a' => 7, 'c' => 7, 'source' => [ 7 ] },
341
+ #
342
+ # ...
343
+ #
344
+ # 'copy_to' and 'copy_from' copy whole fields. 'move_to' and 'move_from'
345
+ # move fields.
346
+ #
347
+ # 'merge_to' and 'merge_from' merge hashes (or add values to
348
+ # arrays), 'push_to' and 'push_from' are aliases for 'merge_to' and
349
+ # 'merge_from' respectively.
350
+ #
351
+ # 'migrate_to' and 'migrate_from' act like 'merge_to' and 'merge_from' but
352
+ # delete the merge source afterwards (like 'move').
353
+ #
354
+ # All those hash/array filter operations understand the '.' field, meaning
355
+ # the hash being filtered itself.
356
+ #
357
+ # # in : { 'x' => { 'a' => 1, 'b' => 2 } })
358
+ # filter 'x', 'merge_to' => '.'
359
+ # # out : { 'x' => { 'a' => 1, 'b' => 2 }, 'a' => 1, 'b' => 2 },
360
+ #
361
+ # === access to 'previous versions' with ~ and ~~
362
+ #
363
+ # Before a filter is applied, a copy of the hash to filter is placed under
364
+ # the '~' key in the hash itself.
365
+ #
366
+ # this filter will at first set the field x to 0, and then reset it to its
367
+ # original value :
368
+ #
369
+ # filter :in => [
370
+ # { :field => 'x', :set => 0 },
371
+ # { :field => 'x', :copy_from => '~.x' }
372
+ # ]
373
+ #
374
+ # For the 'filter' expression, '~~' contains the same thing as '~', but
375
+ # for the :filter attribute, it contains the hash (workitem fields) as
376
+ # it was when the expression with the :filter attribute got reached (applied).
377
+ #
378
+ # === 'restore' and 'restore_from'
379
+ #
380
+ # Since these two filter operations leverage '~~', they're not very useful
381
+ # for the 'filter' expression. But they make lots of sense for the :filter
382
+ # attribute.
383
+ #
384
+ # # in : { 'x' => 'a', 'y' => 'a' },
385
+ # filter :in => [
386
+ # { 'field' => 'x', 'set' => 'X' },
387
+ # { 'field' => 'y', 'set' => 'Y' },
388
+ # { 'field' => '/^.$/', 'restore' => true } ]
389
+ # # out : { 'x' => 'a', 'y' => 'a' },
390
+ #
391
+ # # in : { 'x' => 'a', 'y' => 'a' },
392
+ # filter :in => [
393
+ # { 'field' => 'A', 'set' => {} },
394
+ # { 'field' => '.', 'merge_to' => 'A' },
395
+ # { 'field' => 'x', 'set' => 'X' },
396
+ # { 'field' => 'y', 'set' => 'Y' },
397
+ # { 'field' => '/^[a-z]$/', 'restore_from' => 'A' },
398
+ # { 'field' => 'A', 'delete' => true } ]
399
+ # # out : { 'x' => 'a', 'y' => 'a' })
400
+ #
401
+ #
402
+ # == short forms
403
+ #
404
+ # Could help make filters a bit more compact.
405
+ #
406
+ # * 'size', 'sz'
407
+ # * 'empty', 'e'
408
+ # * 'in', 'i'
409
+ # * 'has', 'h'
410
+ # * 'type', 't'
411
+ # * 'match', 'm'
412
+ # * 'smatch', 'sm'
413
+ # * 'valid', 'v'
414
+ #
415
+ # * 'remove', 'rm', 'delete', 'del'
416
+ # * 'set', 's'
417
+ # * 'copy_to', 'cp_to'
418
+ # * 'move_to', 'mv_to'
419
+ # * 'merge_to', 'mg_to'
420
+ # * 'migrate_to', 'mi_to'
421
+ # * 'restore', 'restore_from', 'rs'
422
+ #
423
+ #
424
+ # == compared to the :filter attribute
425
+ #
426
+ # The :filter attribute accepts participant names, but for this filter
427
+ # expression, it makes no sense accepting partipants... Simply invoke
428
+ # the participant as usual.
429
+ #
430
+ # The 'restore' operation makes lots of sense for the :filter attribute
431
+ # though.
432
+ #
433
+ class FilterExpression < FlowExpression
434
+
435
+ names :filter
436
+
437
+ def apply
438
+
439
+ filter = referenced_filter || complete_filter || one_line_filter
440
+
441
+ record = filter.first.delete('record') rescue nil
442
+ flush = filter.first.delete('flush') rescue nil
443
+
444
+ record = '__validation_errors__' if record == true
445
+
446
+ opts = {
447
+ :double_tilde => parent_id ?
448
+ (parent.h.applied_workitem['fields'] rescue nil) : nil,
449
+ :no_raise => record
450
+ }
451
+ #
452
+ # parent_fields are placed in the ^^ available to the filter
453
+
454
+ fields = Ruote.filter(filter, h.applied_workitem['fields'], opts)
455
+
456
+ if record and fields.is_a?(Array)
457
+ #
458
+ # validation failed, :record requested, list deviations in
459
+ # the given field name
460
+
461
+ (flush ?
462
+ h.applied_workitem['fields'][record] = [] :
463
+ h.applied_workitem['fields'][record] ||= []
464
+ ).concat(fields)
465
+
466
+ reply_to_parent(h.applied_workitem)
467
+
468
+ else
469
+ #
470
+ # filtering successful
471
+
472
+ reply_to_parent(h.applied_workitem.merge('fields' => fields))
473
+ end
474
+ end
475
+
476
+ def reply(workitem)
477
+
478
+ # never called
479
+ end
480
+
481
+ protected
482
+
483
+ def referenced_filter
484
+
485
+ prefix, key = attribute_text.split(':')
486
+
487
+ return nil unless %w[ v var variable f field ].include?(prefix)
488
+
489
+ filter = prefix.match(/^v/) ?
490
+ lookup_variable(key) : Ruote.lookup(h.applied_workitem['fields'], key)
491
+
492
+ if filter.is_a?(Hash) and i = filter['in']
493
+ return i
494
+ end
495
+
496
+ filter
497
+ end
498
+
499
+ def complete_filter
500
+
501
+ return nil if attribute_text != ''
502
+
503
+ attribute(:in)
504
+ end
505
+
506
+ def one_line_filter
507
+
508
+ [ attributes.inject({}) { |h, (k, v)|
509
+ if v.nil?
510
+ h['field'] = k
511
+ else
512
+ h[k] = v
513
+ end
514
+ h
515
+ } ]
516
+ end
517
+ end
518
+ end
519
+