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
@@ -23,9 +23,7 @@
23
23
  #++
24
24
 
25
25
 
26
- #require 'rufus/treechecker'
27
- # is loaded only when needed
28
-
26
+ require 'rufus/treechecker'
29
27
  require 'fileutils'
30
28
 
31
29
 
@@ -37,28 +35,27 @@ module Ruote
37
35
  #
38
36
  class TreeChecker
39
37
 
40
- def initialize (context)
38
+ def initialize(context)
41
39
 
42
40
  (context['use_ruby_treechecker'] == false) and return
43
41
 
44
- require 'rufus/treechecker' # gem 'rufus-treechecker'
45
- # load only when needed
46
-
47
- @checker = Rufus::TreeChecker.new do
42
+ checker = Rufus::TreeChecker.new do
48
43
 
49
44
  exclude_fvccall :abort, :exit, :exit!
50
45
  exclude_fvccall :system, :fork, :syscall, :trap, :require, :load
46
+ exclude_fvccall :at_exit
51
47
 
52
48
  #exclude_call_to :class
53
49
  exclude_fvcall :private, :public, :protected
54
50
 
55
- #exclude_def # no method definition
51
+ #exclude_raise # no raise or throw
52
+
53
+ exclude_def # no method definition
56
54
  exclude_eval # no eval, module_eval or instance_eval
57
55
  exclude_backquotes # no `rm -fR the/kitchen/sink`
58
56
  exclude_alias # no alias or aliast_method
59
57
  exclude_global_vars # $vars are off limits
60
58
  exclude_module_tinkering # no module opening
61
- exclude_raise # no raise or throw
62
59
 
63
60
  exclude_rebinding Kernel # no 'k = Kernel'
64
61
 
@@ -73,29 +70,59 @@ module Ruote
73
70
  exclude_call_to :instance_variable_get, :instance_variable_set
74
71
  end
75
72
 
76
- @cchecker = @checker.clone # and not dup
77
- @cchecker.add_rules do
78
- at_root do
79
- exclude_head [ :block ] # preventing 'a < b; do_sthing_evil()'
80
- exclude_head [ :lasgn ] # preventing 'a = 3'
81
- end
73
+ # the checker used when reading process definitions
74
+
75
+ @def_checker = checker.clone # and not dup
76
+ @def_checker.add_rules do
77
+ exclude_raise # no raise or throw
78
+ end
79
+ @def_checker.freeze
80
+
81
+ # the checker used when dealing with BlockParticipant code
82
+
83
+ @blo_checker = checker.clone # and not dup
84
+ @blo_checker.freeze
85
+
86
+ ## the checker used when dealing with conditionals
87
+ #
88
+ #@con_checker = checker.clone # and not dup
89
+ #@con_checker.add_rules do
90
+ # exclude_raise # no raise or throw
91
+ # at_root do
92
+ # exclude_head [ :block ] # preventing 'a < b; do_sthing_evil()'
93
+ # exclude_head [ :lasgn ] # preventing 'a = 3'
94
+ # end
95
+ #end
96
+ #@con_checker.freeze
97
+ #
98
+ # lib/ruote/exp/condition.rb doesn't use this treechecker
99
+ # kept (commented out) for 'documentation'
100
+
101
+ # the checker used when dealing with code in $(ruby:xxx}
102
+
103
+ @dol_checker = checker.clone # and not dup
104
+ @dol_checker.add_rules do
105
+ exclude_raise # no raise or throw
82
106
  end
107
+ @dol_checker.freeze
83
108
 
84
- @checker.freeze
85
- @cchecker.freeze
86
109
  freeze
87
- #
88
110
  # preventing further modifications
89
111
  end
90
112
 
91
- def check (ruby_code)
113
+ def definition_check(ruby_code)
114
+
115
+ @def_checker.check(ruby_code) if @def_checker
116
+ end
117
+
118
+ def block_check(ruby_code)
92
119
 
93
- @checker.check(ruby_code) if @checker
120
+ @blo_checker.check(ruby_code) if @blo_checker
94
121
  end
95
122
 
96
- def check_conditional (ruby_code)
123
+ def dollar_check(ruby_code)
97
124
 
98
- @cchecker.check(ruby_code) if @checker
125
+ @dol_checker.check(ruby_code) if @dol_checker
99
126
  end
100
127
  end
101
128
  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
@@ -29,7 +29,7 @@ module Ruote
29
29
  #
30
30
  # http://www.graphviz.org
31
31
  #
32
- def self.tree_to_dot (tree, name='ruote process definition')
32
+ def self.tree_to_dot(tree, name='ruote process definition')
33
33
 
34
34
  s = "digraph \"#{name}\" {\n"
35
35
  s << branch_to_dot('0', tree).join("\n")
@@ -38,7 +38,7 @@ module Ruote
38
38
 
39
39
  protected
40
40
 
41
- def self.branch_to_dot (expid, exp)
41
+ def self.branch_to_dot(expid, exp)
42
42
 
43
43
  [
44
44
  " \"#{expid}\" "+
@@ -47,7 +47,7 @@ module Ruote
47
47
  children_to_dot(expid, exp)
48
48
  end
49
49
 
50
- def self.children_to_dot (expid, exp)
50
+ def self.children_to_dot(expid, exp)
51
51
 
52
52
  exp_name = exp[0]
53
53
  child_count = exp[2].size
@@ -0,0 +1,440 @@
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/misc'
26
+ require 'ruote/util/lookup'
27
+
28
+
29
+ module Ruote
30
+
31
+ #
32
+ # An error class for validation errors gathered during filtering.
33
+ #
34
+ class ValidationError < StandardError
35
+
36
+ attr_reader :deviations
37
+
38
+ def initialize(deviations)
39
+ @deviations = deviations
40
+ super("validation failed with #{@deviations.size} deviation(s)")
41
+ end
42
+ end
43
+
44
+ # Given a filter (a list of rules) and a hash (probably workitem fields)
45
+ # performs the validations / transformations dictated by the rules.
46
+ #
47
+ # See the Ruote::Exp::FilterExpression for more information.
48
+ #
49
+ def self.filter(filter, hash, options={})
50
+
51
+ raise ArgumentError.new(
52
+ "not a filter : #{filter}"
53
+ ) unless filter.is_a?(Array)
54
+
55
+ hash = Rufus::Json.dup(hash)
56
+
57
+ hash['~'] = Rufus::Json.dup(hash)
58
+ hash['~~'] = Rufus::Json.dup(options[:double_tilde] || hash)
59
+ # the 'originals'
60
+
61
+ deviations = filter.collect { |rule|
62
+ RuleSession.new(hash, rule).run
63
+ }.flatten(1)
64
+
65
+ hash.delete('~')
66
+ hash.delete('~~')
67
+ # remove the 'originals'
68
+
69
+ if deviations.empty?
70
+ hash
71
+ elsif options[:no_raise]
72
+ deviations
73
+ else
74
+ raise ValidationError.new(deviations)
75
+ end
76
+ end
77
+
78
+ # :nodoc:
79
+ #
80
+ # The class used to run a rule (a line of a filter).
81
+ #
82
+ class RuleSession
83
+
84
+ SKIP = %w[ and or fields field f ]
85
+ NUMBER_CLASSES = [ Fixnum, Float ]
86
+ BOOLEAN_CLASSES = [ TrueClass, FalseClass ]
87
+ TILDE = /^~/
88
+ RTILDE = /^\^~/
89
+
90
+ def initialize(hash, rule)
91
+
92
+ @hash = hash
93
+ @rule = rule
94
+
95
+ fl = @rule['fields'] || @rule['field'] || @rule['f']
96
+
97
+ raise ArgumentError.new(
98
+ "filter is missing a 'fields', 'field' or 'f' arg at #{@rule.inspect}"
99
+ ) unless fl
100
+
101
+ if fl.is_a?(String)
102
+ fl = fl.gsub(/!/, '\.') if REGEX_IN_STRING.match(fl)
103
+ fl = Ruote.regex_or_s(fl)
104
+ end
105
+
106
+ @fields = if fl.is_a?(Regexp)
107
+
108
+ # when restoring, you look at the old keys, not the current ones
109
+
110
+ keys = Ruote.flatten_keys(@rule['restore'] ? @hash['~~'] : @hash)
111
+ keys = keys.reject { |k| TILDE.match(k) } unless RTILDE.match(fl.source)
112
+
113
+ # now only keep the keys that match our regexp
114
+
115
+ keys.inject([]) { |a, k|
116
+ if m = fl.match(k)
117
+ a << [ k, Ruote.lookup(@hash, k), m[1..-1] ]
118
+ end
119
+ a
120
+ }
121
+
122
+ else
123
+
124
+ (fl.is_a?(Array) ? fl : fl.to_s.split(',')).collect { |field|
125
+ field = field.strip
126
+ [ field, Ruote.lookup(@hash, field), nil ]
127
+ }
128
+ end
129
+ end
130
+
131
+ def run
132
+
133
+ @fields.collect { |field, value, matches|
134
+
135
+ valid = nil
136
+
137
+ @rule.each do |k, v|
138
+
139
+ next if SKIP.include?(k)
140
+
141
+ m = "_#{k}"
142
+ next unless self.respond_to?(m)
143
+
144
+ r = self.send(m, field, value, matches, k, v)
145
+
146
+ valid = false if r == false
147
+ end
148
+
149
+ raise_or_and(valid, field, value)
150
+
151
+ }.compact
152
+ end
153
+
154
+ protected
155
+
156
+ def _remove(field, value, matches, m, v)
157
+
158
+ Ruote.unset(@hash, field)
159
+
160
+ nil
161
+ end
162
+ alias _rm _remove
163
+ alias _delete _remove
164
+ alias _del _remove
165
+
166
+ def _set(field, value, matches, m, v)
167
+
168
+ Ruote.set(@hash, field, Rufus::Json.dup(v))
169
+
170
+ nil
171
+ end
172
+ alias _s _set
173
+
174
+ def adjust_target(target, matches)
175
+
176
+ target.gsub(/\\\d+/) { |digit| matches[digit.to_i - 1] rescue '' }
177
+ end
178
+
179
+ def _copy_to(field, value, matches, m, v)
180
+
181
+ v = adjust_target(v, matches)
182
+
183
+ Ruote.set(@hash, v, Rufus::Json.dup(value))
184
+ Ruote.unset(@hash, field) if m == 'move_to' or m == 'mv_to'
185
+
186
+ nil
187
+ end
188
+ alias _cp_to _copy_to
189
+ alias _move_to _copy_to
190
+ alias _mv_to _copy_to
191
+
192
+
193
+ def _copy_from(field, value, matches, m, v)
194
+
195
+ Ruote.set(@hash, field, Rufus::Json.dup(Ruote.lookup(@hash, v)))
196
+ Ruote.unset(@hash, v) if m == 'move_from' or m == 'mv_from'
197
+
198
+ nil
199
+ end
200
+ alias _cp_from _copy_from
201
+ alias _move_from _copy_from
202
+ alias _mv_from _copy_from
203
+
204
+ # Used by both _merge_to and _merge_from
205
+ #
206
+ def do_merge(field, target, value)
207
+
208
+ value = Rufus::Json.dup(value)
209
+
210
+ if target.is_a?(Array)
211
+ target.push(value)
212
+ elsif value.is_a?(Hash)
213
+ target.merge!(value)
214
+ else # deal with non Hash
215
+ target[field.split('.').last] = value
216
+ end
217
+
218
+ target.delete('~')
219
+ target.delete('~~')
220
+ end
221
+
222
+ def _merge_to(field, value, matches, m, v)
223
+
224
+ target = Ruote.lookup(@hash, v)
225
+
226
+ return unless target.respond_to?(:merge!) or target.is_a?(Array)
227
+
228
+ do_merge(field, target, value)
229
+
230
+ Ruote.unset(@hash, field) if m == 'migrate_to' or m == 'mi_to'
231
+
232
+ nil
233
+ end
234
+ alias _mg_to _merge_to
235
+ alias _push_to _merge_to
236
+ alias _pu_to _merge_to
237
+ alias _migrate_to _merge_to
238
+ alias _mi_to _merge_to
239
+
240
+ def _merge_from(field, value, matches, m, v)
241
+
242
+ return unless value.respond_to?(:merge!) or value.is_a?(Array)
243
+
244
+ do_merge(v, value, Ruote.lookup(@hash, v))
245
+
246
+ Ruote.unset(@hash, v) if v != '.' and m.match(/^mi(grate)?_from$/)
247
+
248
+ nil
249
+ end
250
+ alias _mg_from _merge_from
251
+ alias _push_from _merge_from
252
+ alias _pu_from _merge_from
253
+ alias _migrate_from _merge_from
254
+ alias _mi_from _merge_from
255
+
256
+ def _restore(field, value, matches, m, v)
257
+
258
+ prefix = v == true ? '~~' : v.to_s
259
+
260
+ Ruote.set(@hash, field, Ruote.lookup(@hash, "#{prefix}.#{field}"))
261
+
262
+ nil
263
+ end
264
+ alias _restore_from _restore
265
+ alias _rs _restore
266
+
267
+ def _size(field, value, matches, m, v)
268
+
269
+ v = v.is_a?(String) ? v.split(',').collect { |i| i.to_i } : Array(v)
270
+
271
+ if value.respond_to?(:size)
272
+ (v.first ? value.size >= v.first : true) and
273
+ (v.last ? value.size <= v.last : true)
274
+ else
275
+ false
276
+ end
277
+ end
278
+ alias _sz _size
279
+
280
+ def _empty(field, value, matches, m, v)
281
+
282
+ value.respond_to?(:empty?) ? value.empty? : false
283
+ end
284
+ alias _e _empty
285
+
286
+ def _in(field, value, matches, m, v)
287
+
288
+ (v.is_a?(Array) ?
289
+ v :
290
+ v.to_s.split(',').collect { |e| e.strip }
291
+ ).include?(value)
292
+ end
293
+ alias _i _in
294
+
295
+ def _has(field, value, matches, m, v)
296
+
297
+ v = v.is_a?(Array) ? v : v.to_s.split(',').collect { |e| e.strip }
298
+
299
+ if value.is_a?(Hash)
300
+ (value.keys & v) == v
301
+ elsif value.is_a?(Array)
302
+ (value & v) == v
303
+ else
304
+ false
305
+ end
306
+ end
307
+ alias _h _has
308
+
309
+ def _type(field, value, matches, m, v)
310
+
311
+ of_type?(value, v)
312
+ end
313
+ alias _t _type
314
+
315
+ TYPE_SPLITTER = /^(?: *, *)?([^,<]+(?:<.+>)?)(.*)$/
316
+
317
+ def split_type(type)
318
+
319
+ result = []
320
+
321
+ loop do
322
+ m = TYPE_SPLITTER.match(type)
323
+ break unless m
324
+ result << m[1]
325
+ type = m[2]
326
+ end
327
+
328
+ result
329
+ end
330
+
331
+ def of_type?(value, types)
332
+
333
+ types = types.is_a?(Array) ? types : split_type(types)
334
+
335
+ types.inject(false) do |valid, type|
336
+
337
+ valid ||= case type
338
+ when 'null', 'nil'
339
+ value == nil
340
+ when 'string'
341
+ value.class == String
342
+ when 'number'
343
+ NUMBER_CLASSES.include?(value.class)
344
+ when /^(array|object|hash)<(.*)>$/
345
+ children_of_type?(value, $~[2])
346
+ when 'object', 'hash'
347
+ value.class == Hash
348
+ when 'array'
349
+ value.class == Array
350
+ when 'boolean', 'bool'
351
+ BOOLEAN_CLASSES.include?(value.class)
352
+ else
353
+ raise ArgumentError.new("unknown type '#{type}'")
354
+ end
355
+
356
+ valid
357
+ end
358
+ end
359
+
360
+ def children_of_type?(values, types)
361
+
362
+ return false unless values.is_a?(Array) or values.is_a?(Hash)
363
+
364
+ values = values.is_a?(Array) ? values : values.values
365
+
366
+ values.each { |v| of_type?(v, types) or return(false) }
367
+
368
+ true
369
+ end
370
+
371
+ def _match(field, value, matches, m, v)
372
+
373
+ value.nil? ? false : value.to_s.match(v) != nil
374
+ end
375
+ alias _m _match
376
+
377
+ def _smatch(field, value, matches, m, v)
378
+
379
+ value.is_a?(String) ? value.match(v) != nil : false
380
+ end
381
+ alias _sm _smatch
382
+
383
+ def _valid(field, value, matches, m, v)
384
+
385
+ v.to_s == 'true'
386
+ end
387
+ alias _v _valid
388
+
389
+ def raise_or_and(valid, field, value)
390
+
391
+ # dealing with :and and :or...
392
+
393
+ if valid == false
394
+
395
+ if o = @rule['or']
396
+ Ruote.set(@hash, field, Rufus::Json.dup(o))
397
+ elsif @rule['and'].nil?
398
+ return [ @rule, field, value ] # validation break
399
+ end
400
+
401
+ elsif a = @rule['and']
402
+
403
+ Ruote.set(@hash, field, Rufus::Json.dup(a))
404
+
405
+ elsif value.nil? and o = (@rule['or'] || @rule['default'])
406
+
407
+ Ruote.set(@hash, field, Rufus::Json.dup(o))
408
+ end
409
+
410
+ nil
411
+ end
412
+ end
413
+
414
+ # Ruote.flatten_keys({ 'a' => 'b', 'c' => [ 1, 2, 3 ] })
415
+ # # =>
416
+ # [ 'a', 'c', 'c.0', 'c.1', 'c.2' ]
417
+ #
418
+ def self.flatten_keys(o, prefix='', accu=[])
419
+
420
+ if o.is_a?(Array)
421
+
422
+ o.each_with_index do |elt, i|
423
+ pre = "#{prefix}#{i}"
424
+ accu << pre
425
+ flatten_keys(elt, pre + '.', accu)
426
+ end
427
+
428
+ elsif o.is_a?(Hash)
429
+
430
+ o.keys.sort.each do |key|
431
+ pre = "#{prefix}#{key}"
432
+ accu << pre
433
+ flatten_keys(o[key], pre + '.', accu)
434
+ end
435
+ end
436
+
437
+ accu
438
+ end
439
+ end
440
+