ruote-maestrodev 2.2.1
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.
- data/CHANGELOG.txt +290 -0
- data/CREDITS.txt +99 -0
- data/LICENSE.txt +21 -0
- data/README.rdoc +88 -0
- data/Rakefile +108 -0
- data/TODO.txt +488 -0
- data/lib/ruote.rb +7 -0
- data/lib/ruote/context.rb +194 -0
- data/lib/ruote/engine.rb +1062 -0
- data/lib/ruote/engine/process_error.rb +122 -0
- data/lib/ruote/engine/process_status.rb +448 -0
- data/lib/ruote/exp/command.rb +87 -0
- data/lib/ruote/exp/commanded.rb +69 -0
- data/lib/ruote/exp/condition.rb +227 -0
- data/lib/ruote/exp/fe_add_branches.rb +138 -0
- data/lib/ruote/exp/fe_apply.rb +154 -0
- data/lib/ruote/exp/fe_cancel_process.rb +78 -0
- data/lib/ruote/exp/fe_command.rb +156 -0
- data/lib/ruote/exp/fe_concurrence.rb +321 -0
- data/lib/ruote/exp/fe_concurrent_iterator.rb +219 -0
- data/lib/ruote/exp/fe_cron.rb +141 -0
- data/lib/ruote/exp/fe_cursor.rb +324 -0
- data/lib/ruote/exp/fe_define.rb +112 -0
- data/lib/ruote/exp/fe_echo.rb +60 -0
- data/lib/ruote/exp/fe_equals.rb +115 -0
- data/lib/ruote/exp/fe_error.rb +82 -0
- data/lib/ruote/exp/fe_filter.rb +648 -0
- data/lib/ruote/exp/fe_forget.rb +88 -0
- data/lib/ruote/exp/fe_given.rb +154 -0
- data/lib/ruote/exp/fe_if.rb +127 -0
- data/lib/ruote/exp/fe_inc.rb +205 -0
- data/lib/ruote/exp/fe_iterator.rb +234 -0
- data/lib/ruote/exp/fe_let.rb +75 -0
- data/lib/ruote/exp/fe_listen.rb +304 -0
- data/lib/ruote/exp/fe_lose.rb +110 -0
- data/lib/ruote/exp/fe_noop.rb +45 -0
- data/lib/ruote/exp/fe_once.rb +215 -0
- data/lib/ruote/exp/fe_participant.rb +287 -0
- data/lib/ruote/exp/fe_read.rb +69 -0
- data/lib/ruote/exp/fe_redo.rb +82 -0
- data/lib/ruote/exp/fe_ref.rb +152 -0
- data/lib/ruote/exp/fe_registerp.rb +110 -0
- data/lib/ruote/exp/fe_reserve.rb +126 -0
- data/lib/ruote/exp/fe_restore.rb +102 -0
- data/lib/ruote/exp/fe_save.rb +72 -0
- data/lib/ruote/exp/fe_sequence.rb +59 -0
- data/lib/ruote/exp/fe_set.rb +154 -0
- data/lib/ruote/exp/fe_subprocess.rb +211 -0
- data/lib/ruote/exp/fe_that.rb +92 -0
- data/lib/ruote/exp/fe_undo.rb +67 -0
- data/lib/ruote/exp/fe_unregisterp.rb +69 -0
- data/lib/ruote/exp/fe_wait.rb +95 -0
- data/lib/ruote/exp/flowexpression.rb +886 -0
- data/lib/ruote/exp/iterator.rb +81 -0
- data/lib/ruote/exp/merge.rb +118 -0
- data/lib/ruote/exp/ro_attributes.rb +212 -0
- data/lib/ruote/exp/ro_filters.rb +136 -0
- data/lib/ruote/exp/ro_persist.rb +154 -0
- data/lib/ruote/exp/ro_variables.rb +189 -0
- data/lib/ruote/exp/ro_vf.rb +68 -0
- data/lib/ruote/fei.rb +260 -0
- data/lib/ruote/id/mnemo_wfid_generator.rb +43 -0
- data/lib/ruote/id/wfid_generator.rb +81 -0
- data/lib/ruote/log/default_history.rb +122 -0
- data/lib/ruote/log/pretty.rb +176 -0
- data/lib/ruote/log/storage_history.rb +159 -0
- data/lib/ruote/log/test_logger.rb +208 -0
- data/lib/ruote/log/wait_logger.rb +64 -0
- data/lib/ruote/part/block_participant.rb +137 -0
- data/lib/ruote/part/code_participant.rb +81 -0
- data/lib/ruote/part/engine_participant.rb +189 -0
- data/lib/ruote/part/local_participant.rb +138 -0
- data/lib/ruote/part/no_op_participant.rb +60 -0
- data/lib/ruote/part/null_participant.rb +54 -0
- data/lib/ruote/part/rev_participant.rb +169 -0
- data/lib/ruote/part/smtp_participant.rb +116 -0
- data/lib/ruote/part/storage_participant.rb +392 -0
- data/lib/ruote/part/template.rb +84 -0
- data/lib/ruote/participant.rb +7 -0
- data/lib/ruote/reader.rb +278 -0
- data/lib/ruote/reader/json.rb +49 -0
- data/lib/ruote/reader/radial.rb +290 -0
- data/lib/ruote/reader/ruby_dsl.rb +186 -0
- data/lib/ruote/reader/xml.rb +99 -0
- data/lib/ruote/receiver/base.rb +212 -0
- data/lib/ruote/storage/base.rb +364 -0
- data/lib/ruote/storage/composite_storage.rb +121 -0
- data/lib/ruote/storage/fs_storage.rb +139 -0
- data/lib/ruote/storage/hash_storage.rb +211 -0
- data/lib/ruote/svc/dispatch_pool.rb +158 -0
- data/lib/ruote/svc/dollar_sub.rb +298 -0
- data/lib/ruote/svc/error_handler.rb +138 -0
- data/lib/ruote/svc/expression_map.rb +97 -0
- data/lib/ruote/svc/participant_list.rb +397 -0
- data/lib/ruote/svc/tracker.rb +172 -0
- data/lib/ruote/svc/treechecker.rb +141 -0
- data/lib/ruote/tree_dot.rb +85 -0
- data/lib/ruote/util/filter.rb +525 -0
- data/lib/ruote/util/hashdot.rb +79 -0
- data/lib/ruote/util/look.rb +128 -0
- data/lib/ruote/util/lookup.rb +127 -0
- data/lib/ruote/util/misc.rb +167 -0
- data/lib/ruote/util/ometa.rb +71 -0
- data/lib/ruote/util/serializer.rb +103 -0
- data/lib/ruote/util/subprocess.rb +88 -0
- data/lib/ruote/util/time.rb +100 -0
- data/lib/ruote/util/tree.rb +58 -0
- data/lib/ruote/version.rb +29 -0
- data/lib/ruote/worker.rb +386 -0
- data/lib/ruote/workitem.rb +394 -0
- data/phil.txt +14 -0
- data/ruote.gemspec +44 -0
- data/test/bm/ci.rb +55 -0
- data/test/bm/ici.rb +71 -0
- data/test/bm/juuman.rb +54 -0
- data/test/bm/launch_bench.rb +37 -0
- data/test/bm/load_26c.rb +97 -0
- data/test/bm/mega.rb +64 -0
- data/test/bm/seq_thousand.rb +31 -0
- data/test/bm/t.rb +35 -0
- data/test/functional/base.rb +247 -0
- data/test/functional/concurrent_base.rb +98 -0
- data/test/functional/crunner.rb +31 -0
- data/test/functional/ct_0_concurrence.rb +65 -0
- data/test/functional/ct_1_iterator.rb +67 -0
- data/test/functional/ct_2_cancel.rb +81 -0
- data/test/functional/eft_0_process_definition.rb +65 -0
- data/test/functional/eft_10_cancel_process.rb +46 -0
- data/test/functional/eft_11_wait.rb +109 -0
- data/test/functional/eft_12_listen.rb +500 -0
- data/test/functional/eft_13_iterator.rb +342 -0
- data/test/functional/eft_14_cursor.rb +456 -0
- data/test/functional/eft_15_loop.rb +69 -0
- data/test/functional/eft_16_if.rb +183 -0
- data/test/functional/eft_17_equals.rb +55 -0
- data/test/functional/eft_18_concurrent_iterator.rb +410 -0
- data/test/functional/eft_19_reserve.rb +136 -0
- data/test/functional/eft_1_echo.rb +68 -0
- data/test/functional/eft_20_save.rb +116 -0
- data/test/functional/eft_21_restore.rb +61 -0
- data/test/functional/eft_22_noop.rb +28 -0
- data/test/functional/eft_23_apply.rb +168 -0
- data/test/functional/eft_24_add_branches.rb +98 -0
- data/test/functional/eft_25_command.rb +28 -0
- data/test/functional/eft_26_error.rb +77 -0
- data/test/functional/eft_27_inc.rb +280 -0
- data/test/functional/eft_28_once.rb +135 -0
- data/test/functional/eft_29_cron.rb +64 -0
- data/test/functional/eft_2_sequence.rb +58 -0
- data/test/functional/eft_30_ref.rb +155 -0
- data/test/functional/eft_31_registerp.rb +130 -0
- data/test/functional/eft_32_lose.rb +93 -0
- data/test/functional/eft_33_let.rb +31 -0
- data/test/functional/eft_34_given.rb +123 -0
- data/test/functional/eft_35_filter.rb +375 -0
- data/test/functional/eft_36_read.rb +95 -0
- data/test/functional/eft_3_participant.rb +149 -0
- data/test/functional/eft_4_set.rb +296 -0
- data/test/functional/eft_5_subprocess.rb +163 -0
- data/test/functional/eft_6_concurrence.rb +304 -0
- data/test/functional/eft_7_forget.rb +61 -0
- data/test/functional/eft_8_undo.rb +114 -0
- data/test/functional/eft_9_redo.rb +138 -0
- data/test/functional/ft_0_worker.rb +65 -0
- data/test/functional/ft_10_dollar.rb +304 -0
- data/test/functional/ft_11_recursion.rb +109 -0
- data/test/functional/ft_12_launchitem.rb +43 -0
- data/test/functional/ft_13_variables.rb +151 -0
- data/test/functional/ft_14_re_apply.rb +324 -0
- data/test/functional/ft_15_timeout.rb +226 -0
- data/test/functional/ft_16_participant_params.rb +98 -0
- data/test/functional/ft_17_conditional.rb +102 -0
- data/test/functional/ft_18_kill.rb +138 -0
- data/test/functional/ft_19_participant_code.rb +67 -0
- data/test/functional/ft_1_process_status.rb +796 -0
- data/test/functional/ft_20_storage_participant.rb +543 -0
- data/test/functional/ft_21_forget.rb +153 -0
- data/test/functional/ft_22_process_definitions.rb +90 -0
- data/test/functional/ft_23_load_defs.rb +79 -0
- data/test/functional/ft_24_block_participant.rb +235 -0
- data/test/functional/ft_25_receiver.rb +207 -0
- data/test/functional/ft_26_participant_rtimeout.rb +179 -0
- data/test/functional/ft_27_var_indirection.rb +128 -0
- data/test/functional/ft_28_null_noop_participants.rb +51 -0
- data/test/functional/ft_29_part_template.rb +60 -0
- data/test/functional/ft_2_errors.rb +380 -0
- data/test/functional/ft_30_smtp_participant.rb +122 -0
- data/test/functional/ft_31_part_blocking.rb +72 -0
- data/test/functional/ft_33_participant_subprocess_priority.rb +32 -0
- data/test/functional/ft_34_cursor_rewind.rb +101 -0
- data/test/functional/ft_35_add_service.rb +56 -0
- data/test/functional/ft_36_storage_history.rb +150 -0
- data/test/functional/ft_37_default_history.rb +109 -0
- data/test/functional/ft_38_participant_more.rb +193 -0
- data/test/functional/ft_39_wait_for.rb +136 -0
- data/test/functional/ft_3_participant_registration.rb +574 -0
- data/test/functional/ft_40_wait_logger.rb +62 -0
- data/test/functional/ft_41_participants.rb +91 -0
- data/test/functional/ft_42_storage_copy.rb +71 -0
- data/test/functional/ft_43_participant_on_reply.rb +87 -0
- data/test/functional/ft_44_var_participant.rb +35 -0
- data/test/functional/ft_45_participant_accept.rb +64 -0
- data/test/functional/ft_46_launch_single.rb +83 -0
- data/test/functional/ft_47_wfid_generator.rb +54 -0
- data/test/functional/ft_48_lose.rb +112 -0
- data/test/functional/ft_49_engine_on_error.rb +201 -0
- data/test/functional/ft_4_cancel.rb +132 -0
- data/test/functional/ft_50_engine_config.rb +22 -0
- data/test/functional/ft_51_misc.rb +67 -0
- data/test/functional/ft_52_case.rb +134 -0
- data/test/functional/ft_53_engine_on_terminate.rb +95 -0
- data/test/functional/ft_54_patterns.rb +104 -0
- data/test/functional/ft_55_engine_participant.rb +303 -0
- data/test/functional/ft_56_filter_attribute.rb +259 -0
- data/test/functional/ft_57_rev_participant.rb +252 -0
- data/test/functional/ft_58_workitem.rb +69 -0
- data/test/functional/ft_59_pause.rb +343 -0
- data/test/functional/ft_5_on_error.rb +384 -0
- data/test/functional/ft_60_code_participant.rb +45 -0
- data/test/functional/ft_61_trailing_fields.rb +34 -0
- data/test/functional/ft_62_exp_name_and_dollar_substitution.rb +35 -0
- data/test/functional/ft_6_on_cancel.rb +221 -0
- data/test/functional/ft_7_tags.rb +177 -0
- data/test/functional/ft_8_participant_consumption.rb +124 -0
- data/test/functional/ft_9_subprocesses.rb +146 -0
- data/test/functional/restart_base.rb +34 -0
- data/test/functional/rt_0_wait.rb +55 -0
- data/test/functional/rt_1_listen.rb +56 -0
- data/test/functional/rt_2_errors.rb +56 -0
- data/test/functional/rt_3_once.rb +70 -0
- data/test/functional/rt_4_cron.rb +64 -0
- data/test/functional/rt_5_timeout.rb +60 -0
- data/test/functional/rtest.rb +8 -0
- data/test/functional/storage_helper.rb +93 -0
- data/test/functional/test.rb +44 -0
- data/test/functional/vertical.rb +46 -0
- data/test/path_helper.rb +15 -0
- data/test/test.rb +13 -0
- data/test/test_helper.rb +28 -0
- data/test/unit/storage.rb +428 -0
- data/test/unit/storages.rb +37 -0
- data/test/unit/test.rb +28 -0
- data/test/unit/ut_0_ruby_reader.rb +223 -0
- data/test/unit/ut_11_lookup.rb +122 -0
- data/test/unit/ut_13_serializer.rb +65 -0
- data/test/unit/ut_14_is_uri.rb +28 -0
- data/test/unit/ut_15_util.rb +57 -0
- data/test/unit/ut_16_reader.rb +225 -0
- data/test/unit/ut_18_engine.rb +47 -0
- data/test/unit/ut_19_part_template.rb +86 -0
- data/test/unit/ut_1_fei.rb +165 -0
- data/test/unit/ut_20_composite_storage.rb +74 -0
- data/test/unit/ut_21_svc_participant_list.rb +46 -0
- data/test/unit/ut_22_filter.rb +1094 -0
- data/test/unit/ut_23_svc_tracker.rb +48 -0
- data/test/unit/ut_24_radial_reader.rb +332 -0
- data/test/unit/ut_25_merge.rb +113 -0
- data/test/unit/ut_3_wait_logger.rb +39 -0
- data/test/unit/ut_4_expmap.rb +20 -0
- data/test/unit/ut_5_tree.rb +54 -0
- data/test/unit/ut_6_condition.rb +303 -0
- data/test/unit/ut_7_workitem.rb +99 -0
- data/test/unit/ut_8_tree_to_dot.rb +72 -0
- data/test/unit/ut_9_xml_reader.rb +61 -0
- metadata +504 -0
@@ -0,0 +1,112 @@
|
|
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
|
+
|
26
|
+
module Ruote::Exp
|
27
|
+
|
28
|
+
#
|
29
|
+
# The main names for this expression are 'define' and 'process_definition'.
|
30
|
+
# It simply encloses a process definition (and gives it a name and revision
|
31
|
+
# if needed).
|
32
|
+
#
|
33
|
+
# pdef = Ruote.process_definition :name => 'test', :revision => '0' do
|
34
|
+
# sequence do
|
35
|
+
# participant :ref => 'alice'
|
36
|
+
# participant :ref => 'bob'
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# It's used for subprocess definitions as well.
|
41
|
+
#
|
42
|
+
# pdef = Ruote.process_definition :name => 'test', :revision => '0' do
|
43
|
+
# sequence do
|
44
|
+
# buy_food
|
45
|
+
# cook_food
|
46
|
+
# end
|
47
|
+
# define 'buy_food' do
|
48
|
+
# participant :ref => 'alice'
|
49
|
+
# end
|
50
|
+
# define :name => 'cook_food' do
|
51
|
+
# participant :ref => 'bob'
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# == like a sequence
|
56
|
+
#
|
57
|
+
# Ruote 2.0 treats the child expressions of a 'define' expression like a
|
58
|
+
# 'sequence' expression does. Thus, this
|
59
|
+
#
|
60
|
+
# pdef = Ruote.process_definition :name => 'test' do
|
61
|
+
# sequence do
|
62
|
+
# buy_food
|
63
|
+
# cook_food
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# is equivalent to
|
68
|
+
#
|
69
|
+
# pdef = Ruote.process_definition :name => 'test' do
|
70
|
+
# buy_food
|
71
|
+
# cook_food
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
class DefineExpression < FlowExpression
|
75
|
+
|
76
|
+
names :define, :process_definition, :workflow_definition
|
77
|
+
|
78
|
+
def apply
|
79
|
+
|
80
|
+
t = self.class.reorganize(tree).last
|
81
|
+
|
82
|
+
name = attribute(:name) || attribute_text
|
83
|
+
|
84
|
+
set_variable(name, [ h.fei['expid'], t ]) if name
|
85
|
+
#
|
86
|
+
# fei.expid : keeping track of the expid/branch for the subprocess
|
87
|
+
# (so that graphical representations match)
|
88
|
+
|
89
|
+
reply_to_parent(h.applied_workitem)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns true if the tree's root expression is a definition
|
93
|
+
# (define, process_definition, ...)
|
94
|
+
#
|
95
|
+
def self.is_definition?(tree)
|
96
|
+
|
97
|
+
self.expression_names.include?(tree.first)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Used by instances of this class and also the expression pool,
|
101
|
+
# when launching a new process instance.
|
102
|
+
#
|
103
|
+
def self.reorganize(tree)
|
104
|
+
|
105
|
+
definitions, bodies = tree[2].partition { |b| is_definition?(b) }
|
106
|
+
name = tree[1]['name'] || tree[1].keys.find { |k| tree[1][k] == nil }
|
107
|
+
|
108
|
+
[ name, [ 'define', tree[1], definitions + bodies ] ]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
@@ -0,0 +1,60 @@
|
|
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
|
+
|
26
|
+
module Ruote::Exp
|
27
|
+
|
28
|
+
#
|
29
|
+
# An expression for echoing text to STDOUT or to a :s_tracer service
|
30
|
+
# (if there is one bound in the engine context).
|
31
|
+
#
|
32
|
+
# sequence do
|
33
|
+
# participant :ref => 'toto'
|
34
|
+
# echo 'toto replied'
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
class EchoExpression < FlowExpression
|
38
|
+
|
39
|
+
names :echo
|
40
|
+
|
41
|
+
def apply
|
42
|
+
|
43
|
+
text = "#{attribute(:text) || attribute_text}\n"
|
44
|
+
|
45
|
+
if t = @context['s_tracer']
|
46
|
+
t << text
|
47
|
+
else
|
48
|
+
print(text)
|
49
|
+
end
|
50
|
+
|
51
|
+
reply_to_parent(h.applied_workitem)
|
52
|
+
end
|
53
|
+
|
54
|
+
def reply(workitem)
|
55
|
+
|
56
|
+
# never called
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,115 @@
|
|
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
|
+
|
26
|
+
module Ruote::Exp
|
27
|
+
|
28
|
+
#
|
29
|
+
# This expression fell out of favour a long ago. At first it was used with
|
30
|
+
# the 'if' expression :
|
31
|
+
#
|
32
|
+
# _if do
|
33
|
+
# equals :field_value => 'customer', :other_value => 'British Petroleum'
|
34
|
+
# participant :ref => 'Allister'
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# but lately, the :test attribute of the 'if' expression is used :
|
38
|
+
#
|
39
|
+
# _if :test => '${f:customer} == British Petroleum' do
|
40
|
+
# participant :ref => 'Allister'
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# In some cases, the 'if' expression vanishes and the :if attribute shared
|
44
|
+
# by all expressions is used :
|
45
|
+
#
|
46
|
+
# participant :ref => 'Al', :if => '${f:customer} == British Petroleum'
|
47
|
+
#
|
48
|
+
#
|
49
|
+
# == attributes
|
50
|
+
#
|
51
|
+
# The 'equals' expression accepts those attributes :
|
52
|
+
#
|
53
|
+
# * :value
|
54
|
+
# * :field_value
|
55
|
+
# * :variable_value
|
56
|
+
# * :val
|
57
|
+
# * :field_val
|
58
|
+
# * :variable_val
|
59
|
+
#
|
60
|
+
# and
|
61
|
+
#
|
62
|
+
# * :other_value
|
63
|
+
# * :other_field_value
|
64
|
+
# * :other_variable_value
|
65
|
+
# * :other_val
|
66
|
+
# * :other_field_val
|
67
|
+
# * :other_variable_val
|
68
|
+
#
|
69
|
+
# With a bit of luck, they make sense on their own.
|
70
|
+
#
|
71
|
+
class EqualsExpression < FlowExpression
|
72
|
+
|
73
|
+
names :equals
|
74
|
+
|
75
|
+
def apply
|
76
|
+
|
77
|
+
vals = grab_values
|
78
|
+
|
79
|
+
h.applied_workitem['fields']['__result__'] = if vals.size < 2
|
80
|
+
false
|
81
|
+
else
|
82
|
+
(vals.first == vals.last)
|
83
|
+
end
|
84
|
+
|
85
|
+
reply_to_parent(h.applied_workitem)
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
OTHER_REGEX = /^other\_(.+)$/
|
91
|
+
|
92
|
+
def grab_values
|
93
|
+
|
94
|
+
keys = attributes.keys.select { |k| ! COMMON_ATT_KEYS.include?(k) }
|
95
|
+
|
96
|
+
keys.collect { |k| grab_value(k) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def grab_value(k)
|
100
|
+
|
101
|
+
attval = attribute(k)
|
102
|
+
|
103
|
+
if m = OTHER_REGEX.match(k)
|
104
|
+
k = m[1]
|
105
|
+
end
|
106
|
+
|
107
|
+
case k
|
108
|
+
when /^f/ then h.applied_workitem['fields'][attval]
|
109
|
+
when /^var/ then lookup_variable(attval)
|
110
|
+
when /^val/ then attval
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
@@ -0,0 +1,82 @@
|
|
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
|
+
|
26
|
+
module Ruote
|
27
|
+
|
28
|
+
#
|
29
|
+
# This error is used by the 'error' expression, when an error is triggered
|
30
|
+
# from the process definition.
|
31
|
+
#
|
32
|
+
class ForcedError < RuntimeError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Ruote::Exp
|
37
|
+
|
38
|
+
#
|
39
|
+
# Triggers an error directly from the process definition.
|
40
|
+
#
|
41
|
+
# Ruote.process_definition :name => 'log1' do
|
42
|
+
# sequence do
|
43
|
+
# perform_inventory
|
44
|
+
# error 'inventory issue', :if => '${f:level} < 1'
|
45
|
+
# order_new_stuff
|
46
|
+
# store_new_stuff
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# Replaying the error will 'unlock' the process.
|
51
|
+
#
|
52
|
+
class ErrorExpression < FlowExpression
|
53
|
+
|
54
|
+
names :error
|
55
|
+
|
56
|
+
def apply
|
57
|
+
|
58
|
+
# making the error occurs in the reply() phase
|
59
|
+
# so that the replay_at_error targets the reply and not the apply
|
60
|
+
|
61
|
+
@context.storage.put_msg(
|
62
|
+
'reply',
|
63
|
+
'fei' => h.fei,
|
64
|
+
'workitem' => h.applied_workitem)
|
65
|
+
end
|
66
|
+
|
67
|
+
def reply(workitem)
|
68
|
+
|
69
|
+
return reply_to_parent(workitem) if h.triggered
|
70
|
+
|
71
|
+
msg = attribute(:msg) || attribute(:message) || attribute_text
|
72
|
+
msg = 'error triggered from process definition' if msg.strip == ''
|
73
|
+
|
74
|
+
h.triggered = true
|
75
|
+
|
76
|
+
persist_or_raise # to keep track of h.triggered
|
77
|
+
|
78
|
+
raise(Ruote::ForcedError.new(msg))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,648 @@
|
|
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
|
+
# Passing a | separated list of field also works :
|
84
|
+
#
|
85
|
+
# filter 'city|region|country', :type => 'string'
|
86
|
+
# # will make sure that at least of one those field is present
|
87
|
+
# # any of the three that is present must hold a string
|
88
|
+
#
|
89
|
+
#
|
90
|
+
# == validations
|
91
|
+
#
|
92
|
+
# === 'type'
|
93
|
+
#
|
94
|
+
# Ruote is a Ruby library, it adopts Ruby "laissez-faire" for workitem
|
95
|
+
# fields, but sometimes, some type oriented validation is necessary.
|
96
|
+
# Ruote limits itself to the types found in the JSON specification with
|
97
|
+
# one or two additions.
|
98
|
+
#
|
99
|
+
# filter 'x', :type => 'string'
|
100
|
+
# filter 'x', :type => 'number'
|
101
|
+
# filter 'x', :type => 'bool'
|
102
|
+
# filter 'x', :type => 'boolean'
|
103
|
+
# filter 'x', :type => 'null'
|
104
|
+
#
|
105
|
+
# filter 'x', :type => 'array'
|
106
|
+
#
|
107
|
+
# filter 'x', :type => 'object'
|
108
|
+
# filter 'x', :type => 'hash'
|
109
|
+
# # 'object' and 'hash' are equivalent
|
110
|
+
#
|
111
|
+
# It's OK to pass multiple types for a field
|
112
|
+
#
|
113
|
+
# filter 'x', :type => 'bool,number'
|
114
|
+
# filter 'x', :type => [ 'string', 'array' ]
|
115
|
+
#
|
116
|
+
# filter 'x', :type => 'string,null'
|
117
|
+
# # a string or null or not set
|
118
|
+
#
|
119
|
+
# The array and the object/hash types accept a subtype for their values
|
120
|
+
# (a hash/object must have string keys anyway).
|
121
|
+
#
|
122
|
+
# filter 'x', :type => 'array<number>'
|
123
|
+
# filter 'x', :type => 'array<string>'
|
124
|
+
# filter 'x', :type => 'array<array<string>>'
|
125
|
+
#
|
126
|
+
# filter 'x', :type => 'array<string,number>'
|
127
|
+
# # an array of strings or numbers (both)
|
128
|
+
# filter 'x', :type => 'array<string>,array<number>'
|
129
|
+
# # an array of strings or an array of numbers
|
130
|
+
#
|
131
|
+
# === 'match' and 'smatch'
|
132
|
+
#
|
133
|
+
# 'match' will check if a field, when turned into a string, matches
|
134
|
+
# a given regular expression.
|
135
|
+
#
|
136
|
+
# filter 'x', :match => '1'
|
137
|
+
# # will match "11", 1, 1.0, "212"
|
138
|
+
#
|
139
|
+
# 'smatch' works the same but only accepts fields that are strings.
|
140
|
+
#
|
141
|
+
# filter 'x', :smatch => '^user_'
|
142
|
+
# # valid only if x's value is a string that starts with "user_"
|
143
|
+
#
|
144
|
+
# === 'size' and 'empty'
|
145
|
+
#
|
146
|
+
# 'size' is valid for values that respond to the #size method (strings
|
147
|
+
# hashes and arrays).
|
148
|
+
#
|
149
|
+
# filter 'x', :size => 4
|
150
|
+
# # will be valid of values like [ 1, 2, 3, 4 ], "toto" or
|
151
|
+
# # { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }
|
152
|
+
#
|
153
|
+
# filter 'x', :size => [ 4, 5 ]
|
154
|
+
# filter 'x', :size => '4,5'
|
155
|
+
# # four to five elements
|
156
|
+
#
|
157
|
+
# filter 'x', :size => [ 4 ]
|
158
|
+
# filter 'x', :size => [ 4, nil ]
|
159
|
+
# filter 'x', :size => '4,'
|
160
|
+
# # four or more elements
|
161
|
+
#
|
162
|
+
# filter 'x', :size => [ nil, 4 ]
|
163
|
+
# filter 'x', :size => ',4'
|
164
|
+
# # four elements or less
|
165
|
+
#
|
166
|
+
# Similarly, the 'empty' check will evaluate to true (ie not raise an
|
167
|
+
# exception) if the value responds to #empty? and is, well, not empty.
|
168
|
+
#
|
169
|
+
# filter 'x', :empty => true
|
170
|
+
#
|
171
|
+
# === 'is'
|
172
|
+
#
|
173
|
+
# Checks if a field holds the given value.
|
174
|
+
#
|
175
|
+
# filter 'x', :is => true
|
176
|
+
# filter 'x', :is => [ 'a', 2, 3 ]
|
177
|
+
#
|
178
|
+
# === 'in'
|
179
|
+
#
|
180
|
+
# Checks if a value is in a given set of values.
|
181
|
+
#
|
182
|
+
# filter 'x', :in => [ 1, 2, 3 ]
|
183
|
+
# filter 'x', :in => "john, jeff, jim"
|
184
|
+
#
|
185
|
+
# === 'has'
|
186
|
+
#
|
187
|
+
# Checks if an array contains certain values
|
188
|
+
#
|
189
|
+
# filter 'x', :has => 1
|
190
|
+
# filter 'x', :has => "x"
|
191
|
+
# filter 'x', :has => [ 1, 7, 12 ]
|
192
|
+
# filter 'x', :has => "abraham, bob, charly"
|
193
|
+
#
|
194
|
+
# Also checks if a hash has certain keys (strings only of course)
|
195
|
+
#
|
196
|
+
# filter 'x', :has => "x"
|
197
|
+
# filter 'x', :has => "abraham, bob, charly"
|
198
|
+
#
|
199
|
+
# === 'includes'
|
200
|
+
#
|
201
|
+
# Checks if an array includes a given value. Works with Hash values as well.
|
202
|
+
#
|
203
|
+
# filter 'x', :includes => 1
|
204
|
+
#
|
205
|
+
# Whereas 'has' accepts multiple values, 'includes' only accepts one (like
|
206
|
+
# Ruby's Array#include?).
|
207
|
+
#
|
208
|
+
# === 'valid'
|
209
|
+
#
|
210
|
+
# Sometimes, it's better to immediately say 'true' or 'false'.
|
211
|
+
#
|
212
|
+
# filter 'x', :valid => 'true'
|
213
|
+
# filter 'x', :valid => 'false'
|
214
|
+
#
|
215
|
+
# Not very useful...
|
216
|
+
#
|
217
|
+
# In fact, it's meant to be used with the dollar notation
|
218
|
+
#
|
219
|
+
# filter 'x', :valid => '${other.field}'
|
220
|
+
# # will be valid if ${other.field} evaluates to 'true'...
|
221
|
+
#
|
222
|
+
# === cumulating validations
|
223
|
+
#
|
224
|
+
# As seen before, type validations can be cumulated.
|
225
|
+
#
|
226
|
+
# filter 'x', :type => 'bool,number'
|
227
|
+
#
|
228
|
+
# Validations can be cumulated as well
|
229
|
+
#
|
230
|
+
# filter 'x', :type => 'array<number>', :has => [ 1, 2 ]
|
231
|
+
# # will be valid if the field 'x' holds an array of numbers
|
232
|
+
# # and that array has 1 and 2 among its elements
|
233
|
+
#
|
234
|
+
# === validation errors
|
235
|
+
#
|
236
|
+
# By defaults a validation error will result in a process error (ie the
|
237
|
+
# process instance will have to be manually fixed and resumed, or there
|
238
|
+
# is a :on_error somewhere dealing automatically with errors).
|
239
|
+
#
|
240
|
+
# It's possible to prevent raising an error and simply record the validation
|
241
|
+
# errors.
|
242
|
+
#
|
243
|
+
# filter 'x', :type => 'bool,number', :record => true
|
244
|
+
#
|
245
|
+
# will enumerate validation errors in the '__validation_errors__' workitem
|
246
|
+
# field.
|
247
|
+
#
|
248
|
+
# filter 'y', :type => 'bool,number', :record => 'verrors'
|
249
|
+
#
|
250
|
+
# will enumerate validation errors in teh 'verrors' workitem field.
|
251
|
+
#
|
252
|
+
# To flush the recording field, use :flush => true
|
253
|
+
#
|
254
|
+
# sequence do
|
255
|
+
# filter 'x', :type => 'string', :record => true
|
256
|
+
# filter 'y', :type => 'number', :record => true, :flush => true
|
257
|
+
# participant 'after'
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# the participant 'after' will only see the result of the second filter.
|
261
|
+
#
|
262
|
+
# For complex filters, if the first rule has :record => true, the
|
263
|
+
# 'recording' will happen for the whole filter.
|
264
|
+
#
|
265
|
+
# sequence do
|
266
|
+
# filter :in => [
|
267
|
+
# { :field => 'x', :type => 'string', :record => true },
|
268
|
+
# { :field => 'y', :type => 'number' } ]
|
269
|
+
# participant 'after'
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
#
|
273
|
+
# == transformations
|
274
|
+
#
|
275
|
+
# So far, only the validation aspect of filter was shown. They can also be
|
276
|
+
# used to transform the workitem.
|
277
|
+
#
|
278
|
+
# filter 'x', :type => 'string', :or => 'missing'
|
279
|
+
# # will replace the value of x by 'missing' if it's not a string
|
280
|
+
#
|
281
|
+
# filter 'z', :remove => true
|
282
|
+
# # will remove the workitem field z
|
283
|
+
#
|
284
|
+
# filter 'a,b,c', 'set' => '---'
|
285
|
+
# # sets the field a, b and c to '---'
|
286
|
+
#
|
287
|
+
# === 'remove'
|
288
|
+
#
|
289
|
+
# Removes a field (or a subfield).
|
290
|
+
#
|
291
|
+
# filter 'z', :remove => true
|
292
|
+
#
|
293
|
+
# === 'default'
|
294
|
+
#
|
295
|
+
# If there is no value for a field, sets it
|
296
|
+
#
|
297
|
+
# filter 'x', 'default' => 0
|
298
|
+
# # will set x to 0, if it's not set or its value is nil
|
299
|
+
#
|
300
|
+
# filter '/^user-.+/', 'default' => 'nemo'
|
301
|
+
# # will set any 'user-...' field to 'nemo' if its value is nil
|
302
|
+
#
|
303
|
+
# === 'or'
|
304
|
+
#
|
305
|
+
# 'or' combines with a condition. The 'or' value is set if the condition
|
306
|
+
# evaluates to false.
|
307
|
+
#
|
308
|
+
# Using 'or' without a condition makes it equivalent to a 'default'.
|
309
|
+
#
|
310
|
+
# filter 'x', 'or' => 0
|
311
|
+
# # will set x to 0, if it's not set or its value is nil
|
312
|
+
#
|
313
|
+
# filter 'x', 'type' => 'number', 'or' => 0
|
314
|
+
# # if x is not set or is not a number, will set it to 0
|
315
|
+
#
|
316
|
+
# Multiple conditions are OK
|
317
|
+
#
|
318
|
+
# filter 'x', 't' => 'array', 'has' => 'cat', 'or' => []
|
319
|
+
# # if x is an array and has the 'cat' element, nothing will happen.
|
320
|
+
# # Else x will be set to [].
|
321
|
+
#
|
322
|
+
# === 'and'
|
323
|
+
#
|
324
|
+
# 'and' is much like 'or', but it triggers if the condition evaluates to true.
|
325
|
+
#
|
326
|
+
# filter 'x', 'type' => number, 'and' => '*removed*'
|
327
|
+
# # if x is a number, it will replace it with '*removed*'
|
328
|
+
#
|
329
|
+
# === 'set'
|
330
|
+
#
|
331
|
+
# Like 'remove' removes unconditionally, 'set' sets a field unconditionally.
|
332
|
+
#
|
333
|
+
# filter 'x', 'set' => 'blue'
|
334
|
+
# # sets the field x to 'blue'
|
335
|
+
#
|
336
|
+
# === copy, merge, migrate / to, from
|
337
|
+
#
|
338
|
+
# # in : { 'x' => 'y' }
|
339
|
+
# filter 'x', 'copy_to' => 'z'
|
340
|
+
# # out : { 'x' => 'y', 'z' => 'y' }
|
341
|
+
#
|
342
|
+
# # in : { 'x' => 'y' }
|
343
|
+
# filter 'z', 'copy_from' => 'x'
|
344
|
+
# # out : { 'x' => 'y', 'z' => 'y' }
|
345
|
+
#
|
346
|
+
# # in : { 'x' => 'y' }
|
347
|
+
# filter 'z', 'copy_from' => 'x'
|
348
|
+
# # out : { 'x' => 'y', 'z' => 'y' }
|
349
|
+
#
|
350
|
+
# # in : { 'a' => %w[ x y ]})
|
351
|
+
# filter '/a\.(.+)/', 'copy_to' => 'b\1'
|
352
|
+
# # out : { 'a' => %w[ x y ], 'b0' => 'x', 'b1' => 'y' },
|
353
|
+
#
|
354
|
+
# # in : { 'a' => %w[ x y ]})
|
355
|
+
# filter '/a!(.+)/', 'copy_to' => 'b\1'
|
356
|
+
# # out : { 'a' => %w[ x y ], 'b0' => 'x', 'b1' => 'y' },
|
357
|
+
# #
|
358
|
+
# # '!' is used as a replacement for '\.' in regexes
|
359
|
+
#
|
360
|
+
# # in : { 'a' => 'b', 'c' => 'd', 'source' => [ 7 ] })
|
361
|
+
# filter '/^.$/', 'copy_from' => 'source.0'
|
362
|
+
# # out : { 'a' => 7, 'c' => 7, 'source' => [ 7 ] },
|
363
|
+
#
|
364
|
+
# ...
|
365
|
+
#
|
366
|
+
# 'copy_to' and 'copy_from' copy whole fields. 'move_to' and 'move_from'
|
367
|
+
# move fields.
|
368
|
+
#
|
369
|
+
# 'merge_to' and 'merge_from' merge hashes (or add values to
|
370
|
+
# arrays), 'push_to' and 'push_from' are aliases for 'merge_to' and
|
371
|
+
# 'merge_from' respectively.
|
372
|
+
#
|
373
|
+
# 'migrate_to' and 'migrate_from' act like 'merge_to' and 'merge_from' but
|
374
|
+
# delete the merge source afterwards (like 'move').
|
375
|
+
#
|
376
|
+
# All those hash/array filter operations understand the '.' field, meaning
|
377
|
+
# the hash being filtered itself.
|
378
|
+
#
|
379
|
+
# # in : { 'x' => { 'a' => 1, 'b' => 2 } })
|
380
|
+
# filter 'x', 'merge_to' => '.'
|
381
|
+
# # out : { 'x' => { 'a' => 1, 'b' => 2 }, 'a' => 1, 'b' => 2 },
|
382
|
+
#
|
383
|
+
# === access to 'previous versions' with ~ and ~~
|
384
|
+
#
|
385
|
+
# Before a filter is applied, a copy of the hash to filter is placed under
|
386
|
+
# the '~' key in the hash itself.
|
387
|
+
#
|
388
|
+
# this filter will at first set the field x to 0, and then reset it to its
|
389
|
+
# original value :
|
390
|
+
#
|
391
|
+
# filter :in => [
|
392
|
+
# { :field => 'x', :set => 0 },
|
393
|
+
# { :field => 'x', :copy_from => '~.x' }
|
394
|
+
# ]
|
395
|
+
#
|
396
|
+
# For the 'filter' expression, '~~' contains the same thing as '~', but
|
397
|
+
# for the :filter attribute, it contains the hash (workitem fields) as
|
398
|
+
# it was when the expression with the :filter attribute got reached (applied).
|
399
|
+
#
|
400
|
+
# === 'restore' and 'restore_from'
|
401
|
+
#
|
402
|
+
# Since these two filter operations leverage '~~', they're not very useful
|
403
|
+
# for the 'filter' expression. But they make lots of sense for the :filter
|
404
|
+
# attribute.
|
405
|
+
#
|
406
|
+
# # in : { 'x' => 'a', 'y' => 'a' },
|
407
|
+
# filter :in => [
|
408
|
+
# { 'field' => 'x', 'set' => 'X' },
|
409
|
+
# { 'field' => 'y', 'set' => 'Y' },
|
410
|
+
# { 'field' => '/^.$/', 'restore' => true } ]
|
411
|
+
# # out : { 'x' => 'a', 'y' => 'a' },
|
412
|
+
#
|
413
|
+
# # in : { 'x' => 'a', 'y' => 'a' },
|
414
|
+
# filter :in => [
|
415
|
+
# { 'field' => 'A', 'set' => {} },
|
416
|
+
# { 'field' => '.', 'merge_to' => 'A' },
|
417
|
+
# { 'field' => 'x', 'set' => 'X' },
|
418
|
+
# { 'field' => 'y', 'set' => 'Y' },
|
419
|
+
# { 'field' => '/^[a-z]$/', 'restore_from' => 'A' },
|
420
|
+
# { 'field' => 'A', 'delete' => true } ]
|
421
|
+
# # out : { 'x' => 'a', 'y' => 'a' })
|
422
|
+
#
|
423
|
+
#
|
424
|
+
# == short forms
|
425
|
+
#
|
426
|
+
# Could help make filters a bit more compact.
|
427
|
+
#
|
428
|
+
# * 'size', 'sz'
|
429
|
+
# * 'empty', 'e'
|
430
|
+
# * 'in', 'i'
|
431
|
+
# * 'has', 'h'
|
432
|
+
# * 'type', 't'
|
433
|
+
# * 'match', 'm'
|
434
|
+
# * 'smatch', 'sm'
|
435
|
+
# * 'valid', 'v'
|
436
|
+
#
|
437
|
+
# * 'remove', 'rm', 'delete', 'del'
|
438
|
+
# * 'set', 's'
|
439
|
+
# * 'copy_to', 'cp_to'
|
440
|
+
# * 'move_to', 'mv_to'
|
441
|
+
# * 'merge_to', 'mg_to'
|
442
|
+
# * 'migrate_to', 'mi_to'
|
443
|
+
# * 'restore', 'restore_from', 'rs'
|
444
|
+
#
|
445
|
+
#
|
446
|
+
# == top-level 'or'
|
447
|
+
#
|
448
|
+
# Filters may be used to transform hashes or to validate them. In both cases
|
449
|
+
# the filters seen until now were like chained by a big AND.
|
450
|
+
#
|
451
|
+
# It's OK to write
|
452
|
+
#
|
453
|
+
# filter :in => [
|
454
|
+
# { 'field' => 'server_href', 'smatch' => '^https?:\/\/' },
|
455
|
+
# 'or',
|
456
|
+
# { 'field' => 'nickname', 'type' => 'string' } ]
|
457
|
+
#
|
458
|
+
# Granted, this is mostly for validation purposes, but it also works
|
459
|
+
# with transformations (as soon as an 'or' child succeeds it's returned
|
460
|
+
# and the other children are not evaluated).
|
461
|
+
#
|
462
|
+
#
|
463
|
+
# == compared to the :filter attribute
|
464
|
+
#
|
465
|
+
# The :filter attribute accepts participant names, but for this filter
|
466
|
+
# expression, it makes no sense accepting partipants... Simply invoke
|
467
|
+
# the participant as usual.
|
468
|
+
#
|
469
|
+
# The 'restore' operation makes lots of sense for the :filter attribute
|
470
|
+
# though.
|
471
|
+
#
|
472
|
+
#
|
473
|
+
# == filtering with rules in a block
|
474
|
+
#
|
475
|
+
# This filter
|
476
|
+
#
|
477
|
+
# filter :in => [
|
478
|
+
# { :field => 'x', :type => 'string' },
|
479
|
+
# { :field => 'y', :type => 'number' }
|
480
|
+
# ]
|
481
|
+
#
|
482
|
+
# can be rewritten as
|
483
|
+
#
|
484
|
+
# filter do
|
485
|
+
# field 'x', :type => 'string'
|
486
|
+
# field 'y', :type => 'number'
|
487
|
+
# end
|
488
|
+
#
|
489
|
+
# The field names can be passed directly as head of each rule :
|
490
|
+
#
|
491
|
+
# filter do
|
492
|
+
# x :type => 'string'
|
493
|
+
# y :type => 'number'
|
494
|
+
# end
|
495
|
+
#
|
496
|
+
class FilterExpression < FlowExpression
|
497
|
+
|
498
|
+
names :filter
|
499
|
+
|
500
|
+
def apply
|
501
|
+
|
502
|
+
filter =
|
503
|
+
referenced_filter || complete_filter || one_line_filter || block_filter
|
504
|
+
|
505
|
+
record = filter.first.delete('record') rescue nil
|
506
|
+
flush = filter.first.delete('flush') rescue nil
|
507
|
+
|
508
|
+
record = '__validation_errors__' if record == true
|
509
|
+
|
510
|
+
opts = {
|
511
|
+
:double_tilde => parent_id ?
|
512
|
+
(parent.h.applied_workitem['fields'] rescue nil) : nil,
|
513
|
+
:no_raise => record
|
514
|
+
}
|
515
|
+
#
|
516
|
+
# parent_fields are placed in the ^^ available to the filter
|
517
|
+
|
518
|
+
fields = Ruote.filter(filter, h.applied_workitem['fields'], opts)
|
519
|
+
|
520
|
+
if record and fields.is_a?(Array)
|
521
|
+
#
|
522
|
+
# validation failed, :record requested, list deviations in
|
523
|
+
# the given field name
|
524
|
+
|
525
|
+
(flush ?
|
526
|
+
h.applied_workitem['fields'][record] = [] :
|
527
|
+
h.applied_workitem['fields'][record] ||= []
|
528
|
+
).concat(fields)
|
529
|
+
|
530
|
+
reply_to_parent(h.applied_workitem)
|
531
|
+
|
532
|
+
else
|
533
|
+
#
|
534
|
+
# filtering successful
|
535
|
+
|
536
|
+
reply_to_parent(h.applied_workitem.merge('fields' => fields))
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
def reply(workitem)
|
541
|
+
|
542
|
+
# never called
|
543
|
+
end
|
544
|
+
|
545
|
+
protected
|
546
|
+
|
547
|
+
# Filter is passed in a block (which is not evaluted as a ruote branch
|
548
|
+
# but immediately translated into a filter.
|
549
|
+
#
|
550
|
+
# pdef = Ruote.process_definition do
|
551
|
+
# filter do
|
552
|
+
# field 'x', :type => 'string'
|
553
|
+
# field 'y', :type => 'number'
|
554
|
+
# end
|
555
|
+
# end
|
556
|
+
#
|
557
|
+
# Note : 'or' is OK
|
558
|
+
#
|
559
|
+
# pdef = Ruote.process_definition do
|
560
|
+
# filter do
|
561
|
+
# field 'x', :type => 'string'
|
562
|
+
# _or
|
563
|
+
# field 'y', :type => 'number'
|
564
|
+
# end
|
565
|
+
# end
|
566
|
+
#
|
567
|
+
def block_filter
|
568
|
+
|
569
|
+
return nil if tree.last.empty?
|
570
|
+
|
571
|
+
tree.last.collect { |line|
|
572
|
+
|
573
|
+
next 'or' if line.first == 'or'
|
574
|
+
|
575
|
+
rule = line[1].inject({}) { |h, (k, v)|
|
576
|
+
if v == nil
|
577
|
+
h['field'] = k
|
578
|
+
else
|
579
|
+
h[k] = v
|
580
|
+
end
|
581
|
+
h
|
582
|
+
}
|
583
|
+
|
584
|
+
rule['field'] ||= line.first
|
585
|
+
|
586
|
+
rule
|
587
|
+
}
|
588
|
+
end
|
589
|
+
|
590
|
+
# Filter is somewhere else (process variable or workitem field)
|
591
|
+
#
|
592
|
+
def referenced_filter
|
593
|
+
|
594
|
+
prefix, key = attribute_text.split(':')
|
595
|
+
|
596
|
+
return nil unless %w[ v var variable f field ].include?(prefix)
|
597
|
+
|
598
|
+
filter = prefix.match(/^v/) ?
|
599
|
+
lookup_variable(key) : Ruote.lookup(h.applied_workitem['fields'], key)
|
600
|
+
|
601
|
+
if filter.is_a?(Hash) and i = filter['in']
|
602
|
+
return i
|
603
|
+
end
|
604
|
+
|
605
|
+
filter
|
606
|
+
end
|
607
|
+
|
608
|
+
# Filter is passed with an :in attribute.
|
609
|
+
#
|
610
|
+
# Ruote.process_definition do
|
611
|
+
# filter :in => [
|
612
|
+
# { :field => 'x', :type => 'string' },
|
613
|
+
# { :field => 'y', :type => 'number' }
|
614
|
+
# ]
|
615
|
+
# end
|
616
|
+
#
|
617
|
+
def complete_filter
|
618
|
+
|
619
|
+
return nil if attribute_text != ''
|
620
|
+
|
621
|
+
attribute(:in)
|
622
|
+
end
|
623
|
+
|
624
|
+
# Filter thanks to the attributes of the expression.
|
625
|
+
#
|
626
|
+
# pdef = Ruote.process_definition do
|
627
|
+
# filter 'x', :type => 'string', :record => true
|
628
|
+
# filter 'y', :type => 'number', :record => true
|
629
|
+
# end
|
630
|
+
#
|
631
|
+
def one_line_filter
|
632
|
+
|
633
|
+
if (attributes.keys - COMMON_ATT_KEYS - %w[ ref original_ref ]).empty?
|
634
|
+
return nil
|
635
|
+
end
|
636
|
+
|
637
|
+
[ attributes.inject({}) { |h, (k, v)|
|
638
|
+
if v.nil?
|
639
|
+
h['field'] = k
|
640
|
+
else
|
641
|
+
h[k] = v
|
642
|
+
end
|
643
|
+
h
|
644
|
+
} ]
|
645
|
+
end
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|