ruote 2.3.0.1 → 2.3.0.2

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 (148) hide show
  1. data/CHANGELOG.txt +23 -0
  2. data/CREDITS.txt +4 -0
  3. data/LICENSE.txt +1 -1
  4. data/lib/ruote.rb +2 -0
  5. data/lib/ruote/context.rb +2 -1
  6. data/lib/ruote/dashboard.rb +169 -13
  7. data/lib/ruote/dboard/mutation.rb +282 -0
  8. data/lib/ruote/dboard/process_error.rb +1 -1
  9. data/lib/ruote/dboard/process_status.rb +61 -48
  10. data/lib/ruote/engine.rb +1 -1
  11. data/lib/ruote/exp/command.rb +1 -1
  12. data/lib/ruote/exp/commanded.rb +1 -1
  13. data/lib/ruote/exp/condition.rb +2 -1
  14. data/lib/ruote/exp/fe_add_branches.rb +1 -1
  15. data/lib/ruote/exp/fe_apply.rb +1 -1
  16. data/lib/ruote/exp/fe_await.rb +97 -48
  17. data/lib/ruote/exp/fe_cancel_process.rb +1 -1
  18. data/lib/ruote/exp/fe_command.rb +2 -3
  19. data/lib/ruote/exp/fe_concurrence.rb +162 -66
  20. data/lib/ruote/exp/fe_concurrent_iterator.rb +25 -7
  21. data/lib/ruote/exp/fe_cron.rb +1 -1
  22. data/lib/ruote/exp/fe_cursor.rb +10 -11
  23. data/lib/ruote/exp/fe_define.rb +1 -1
  24. data/lib/ruote/exp/fe_echo.rb +1 -1
  25. data/lib/ruote/exp/fe_equals.rb +1 -1
  26. data/lib/ruote/exp/fe_error.rb +1 -1
  27. data/lib/ruote/exp/fe_filter.rb +1 -1
  28. data/lib/ruote/exp/fe_forget.rb +1 -1
  29. data/lib/ruote/exp/fe_given.rb +1 -1
  30. data/lib/ruote/exp/fe_if.rb +87 -7
  31. data/lib/ruote/exp/fe_inc.rb +1 -1
  32. data/lib/ruote/exp/fe_iterator.rb +1 -1
  33. data/lib/ruote/exp/fe_listen.rb +1 -1
  34. data/lib/ruote/exp/fe_lose.rb +1 -1
  35. data/lib/ruote/exp/fe_noop.rb +1 -1
  36. data/lib/ruote/exp/fe_on_error.rb +1 -1
  37. data/lib/ruote/exp/fe_once.rb +1 -1
  38. data/lib/ruote/exp/fe_participant.rb +49 -16
  39. data/lib/ruote/exp/fe_read.rb +1 -1
  40. data/lib/ruote/exp/fe_redo.rb +1 -1
  41. data/lib/ruote/exp/fe_ref.rb +1 -1
  42. data/lib/ruote/exp/fe_registerp.rb +1 -1
  43. data/lib/ruote/exp/fe_reserve.rb +1 -1
  44. data/lib/ruote/exp/fe_restore.rb +1 -7
  45. data/lib/ruote/exp/fe_save.rb +1 -1
  46. data/lib/ruote/exp/fe_sequence.rb +1 -1
  47. data/lib/ruote/exp/fe_set.rb +1 -1
  48. data/lib/ruote/exp/fe_stall.rb +1 -1
  49. data/lib/ruote/exp/fe_subprocess.rb +1 -1
  50. data/lib/ruote/exp/fe_that.rb +1 -1
  51. data/lib/ruote/exp/fe_undo.rb +1 -1
  52. data/lib/ruote/exp/fe_unregisterp.rb +1 -1
  53. data/lib/ruote/exp/fe_wait.rb +1 -1
  54. data/lib/ruote/exp/flow_expression.rb +117 -8
  55. data/lib/ruote/exp/iterator.rb +1 -1
  56. data/lib/ruote/exp/ro_attributes.rb +1 -1
  57. data/lib/ruote/exp/ro_filters.rb +1 -1
  58. data/lib/ruote/exp/ro_on_x.rb +4 -2
  59. data/lib/ruote/exp/ro_persist.rb +1 -1
  60. data/lib/ruote/exp/ro_timers.rb +1 -1
  61. data/lib/ruote/exp/ro_variables.rb +1 -1
  62. data/lib/ruote/extract.rb +125 -0
  63. data/lib/ruote/fei.rb +10 -73
  64. data/lib/ruote/id/mnemo_wfid_generator.rb +1 -1
  65. data/lib/ruote/id/wfid_generator.rb +1 -1
  66. data/lib/ruote/log/default_history.rb +17 -3
  67. data/lib/ruote/log/fancy_printing.rb +12 -32
  68. data/lib/ruote/log/storage_history.rb +1 -1
  69. data/lib/ruote/log/wait_logger.rb +15 -7
  70. data/lib/ruote/merge.rb +123 -0
  71. data/lib/ruote/observer.rb +1 -1
  72. data/lib/ruote/part/block_participant.rb +1 -1
  73. data/lib/ruote/part/code_participant.rb +1 -1
  74. data/lib/ruote/part/engine_participant.rb +1 -1
  75. data/lib/ruote/part/local_participant.rb +9 -1
  76. data/lib/ruote/part/no_op_participant.rb +1 -1
  77. data/lib/ruote/part/null_participant.rb +1 -1
  78. data/lib/ruote/part/participant.rb +1 -1
  79. data/lib/ruote/part/rev_participant.rb +1 -1
  80. data/lib/ruote/part/smtp_participant.rb +1 -1
  81. data/lib/ruote/part/storage_participant.rb +18 -1
  82. data/lib/ruote/part/template.rb +1 -1
  83. data/lib/ruote/reader.rb +1 -1
  84. data/lib/ruote/reader/json.rb +1 -1
  85. data/lib/ruote/reader/radial.rb +4 -4
  86. data/lib/ruote/reader/ruby_dsl.rb +1 -1
  87. data/lib/ruote/reader/xml.rb +1 -1
  88. data/lib/ruote/receiver/base.rb +13 -1
  89. data/lib/ruote/storage/base.rb +8 -14
  90. data/lib/ruote/storage/composite_storage.rb +1 -1
  91. data/lib/ruote/storage/fs_storage.rb +1 -1
  92. data/lib/ruote/storage/hash_storage.rb +2 -1
  93. data/lib/ruote/svc/dispatch_pool.rb +29 -18
  94. data/lib/ruote/svc/dollar_sub.rb +5 -8
  95. data/lib/ruote/svc/error_handler.rb +1 -1
  96. data/lib/ruote/svc/expression_map.rb +1 -1
  97. data/lib/ruote/svc/participant_list.rb +8 -5
  98. data/lib/ruote/svc/tracker.rb +154 -56
  99. data/lib/ruote/svc/treechecker.rb +1 -1
  100. data/lib/ruote/tree_dot.rb +1 -1
  101. data/lib/ruote/util/deep.rb +4 -2
  102. data/lib/ruote/util/filter.rb +1 -1
  103. data/lib/ruote/util/hashdot.rb +1 -1
  104. data/lib/ruote/util/look.rb +1 -1
  105. data/lib/ruote/util/lookup.rb +1 -1
  106. data/lib/ruote/util/misc.rb +51 -1
  107. data/lib/ruote/util/mpatch.rb +1 -1
  108. data/lib/ruote/util/ometa.rb +1 -1
  109. data/lib/ruote/util/subprocess.rb +1 -1
  110. data/lib/ruote/util/time.rb +3 -3
  111. data/lib/ruote/util/tree.rb +43 -4
  112. data/lib/ruote/version.rb +2 -2
  113. data/lib/ruote/worker.rb +30 -18
  114. data/lib/ruote/workitem.rb +1 -1
  115. data/ruote.gemspec +6 -2
  116. data/test/functional/base.rb +0 -1
  117. data/test/functional/concurrent_base.rb +1 -1
  118. data/test/functional/eft_14_cursor.rb +42 -52
  119. data/test/functional/eft_16_if.rb +24 -16
  120. data/test/functional/eft_18_concurrent_iterator.rb +31 -1
  121. data/test/functional/eft_6_concurrence.rb +149 -34
  122. data/test/functional/ft_10_dollar.rb +14 -30
  123. data/test/functional/ft_12_launchitem.rb +15 -0
  124. data/test/functional/ft_1_process_status.rb +62 -13
  125. data/test/functional/ft_20_storage_participant.rb +25 -0
  126. data/test/functional/ft_38_participant_more.rb +1 -1
  127. data/test/functional/ft_42_storage_copy.rb +1 -3
  128. data/test/functional/ft_43_participant_on_reply.rb +63 -5
  129. data/test/functional/ft_66_flank.rb +41 -0
  130. data/test/functional/ft_6_on_cancel.rb +9 -18
  131. data/test/functional/ft_71_retries.rb +25 -12
  132. data/test/functional/ft_79_attach.rb +138 -0
  133. data/test/functional/ft_7_tags.rb +27 -0
  134. data/test/functional/ft_80_pause_on_apply.rb +64 -0
  135. data/test/functional/ft_81_mutation.rb +417 -0
  136. data/test/functional/ft_82_await_attribute.rb +84 -0
  137. data/test/functional/ft_83_trackers.rb +79 -0
  138. data/test/functional/storage.rb +3 -4
  139. data/test/unit/ut_12_wait_logger.rb +41 -3
  140. data/test/unit/ut_15_util.rb +30 -0
  141. data/test/unit/ut_17_merge.rb +54 -53
  142. data/test/unit/ut_1_fei.rb +2 -2
  143. data/test/unit/ut_24_radial_reader.rb +7 -0
  144. data/test/unit/ut_26_deep.rb +14 -0
  145. data/test/unit/ut_5_tree.rb +38 -28
  146. metadata +206 -169
  147. data/couch_url.txt +0 -1
  148. data/lib/ruote/exp/merge.rb +0 -134
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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
@@ -173,17 +173,18 @@ module Ruote
173
173
  pinfo = participant_name.is_a?(String) ?
174
174
  lookup_info(participant_name, workitem) : participant_name
175
175
 
176
- pinfo ?
177
- instantiate(pinfo, opts) : nil
176
+ instantiate(pinfo, opts)
178
177
  end
179
178
 
180
- # Given a participant name, returns
179
+ # Given a participant name, returns participant details.
181
180
  #
182
181
  # Returns nil if there is no participant registered that covers the given
183
182
  # participant name.
184
183
  #
185
184
  def lookup_info(pname, workitem)
186
185
 
186
+ return nil unless pname
187
+
187
188
  wi = workitem ?
188
189
  Ruote::Workitem.new(workitem.merge('participant_name' => pname)) :
189
190
  nil
@@ -205,10 +206,12 @@ module Ruote
205
206
  nil
206
207
  end
207
208
 
208
- # Returns an instance of a participant
209
+ # Returns an instance of a participant.
209
210
  #
210
211
  def instantiate(pinfo, opts={})
211
212
 
213
+ return nil unless pinfo
214
+
212
215
  pa_class_name, options = pinfo
213
216
 
214
217
  if rp = options['require_path']
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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
@@ -39,18 +39,109 @@ module Ruote
39
39
  @context = context
40
40
  end
41
41
 
42
- # The context calls this method for each successfully processed msg
43
- # in the worker.
42
+ # The worker calls this method via the context before each msg gets
43
+ # processed.
44
44
  #
45
- def on_msg(message)
45
+ def on_pre_msg(msg)
46
+
47
+ on_message(true, msg)
48
+ end
49
+
50
+ # The worker calls this method via the context after each successful
51
+ # msg processing.
52
+ #
53
+ def on_msg(msg)
54
+
55
+ on_message(false, msg)
56
+ end
57
+
58
+ # Adds a tracker (usually when a 'listen' expression gets applied).
59
+ #
60
+ # The tracker_id may be nil (one will then get generated).
61
+ #
62
+ # Returns the tracker_id.
63
+ #
64
+ def add_tracker(wfid, action, tracker_id, conditions, msg)
65
+
66
+ tracker_id ||= [
67
+ 'tracker', wfid, action,
68
+ Ruote.generate_subid(conditions.hash.to_s + msg.hash.to_s)
69
+ ].collect(&:to_s).join('_')
70
+
71
+ conditions =
72
+ conditions && conditions.remap { |(k, v), h| h[k] = Array(v) }
73
+
74
+ doc = @context.storage.get_trackers
75
+
76
+ doc['trackers'][tracker_id] =
77
+ { 'wfid' => wfid,
78
+ 'action' => action,
79
+ 'id' => tracker_id,
80
+ 'conditions' => conditions,
81
+ 'msg' => msg }
82
+
83
+ r = @context.storage.put(doc)
84
+
85
+ add_tracker(wfid, action, tracker_id, conditions, msg) if r
86
+ # the put failed, have to redo the work
87
+
88
+ tracker_id
89
+ end
90
+
91
+ # Removes a tracker (usually when a 'listen' expression replies to its
92
+ # parent expression or is cancelled).
93
+ #
94
+ def remove_tracker(fei_sid_or_id, wfid=nil)
95
+
96
+ tracker_id =
97
+ if fei_sid_or_id.is_a?(String)
98
+ fei_sid_or_id
99
+ else
100
+ Ruote.to_storage_id(fei_sid_or_id)
101
+ end
102
+
103
+ remove([ tracker_id ], wfid)
104
+ end
105
+
106
+ protected
107
+
108
+ # Removes a set of tracker ids and updated the tracker document.
109
+ #
110
+ def remove(tracker_ids, wfid)
111
+
112
+ return if tracker_ids.empty?
113
+
114
+ doc ||= @context.storage.get_trackers(wfid)
115
+
116
+ return if (doc['trackers'].keys & tracker_ids).empty?
117
+
118
+ doc['wfid'] = wfid
119
+ # a little helper for some some storage implementations like ruote-swf
120
+ # they need to know what workflow execution is targetted.
121
+
122
+ tracker_ids.each { |ti| doc['trackers'].delete(ti) }
123
+ r = @context.storage.put(doc)
124
+
125
+ remove(tracker_ids, wfid) if r
126
+ # the put failed, have to redo the work
127
+ end
128
+
129
+ # The method behind on_pre_msg and on_msg. Filters msgs against trackers.
130
+ # Triggers trackers if there is a match.
131
+ #
132
+ def on_message(pre, message)
46
133
 
47
- m_error = message['error']
48
134
  m_wfid = message['wfid'] || (message['fei']['wfid'] rescue nil)
135
+ m_error = message['error']
136
+
49
137
  m_action = message['action']
138
+ m_action = "pre_#{m_action}" if pre
50
139
 
51
140
  msg = m_action == 'error_intercepted' ? message['msg'] : message
52
141
 
53
- @context.storage.get_trackers['trackers'].each do |tracker_id, tracker|
142
+ ids_to_remove = []
143
+
144
+ trackers.each do |tracker_id, tracker|
54
145
 
55
146
  # filter msgs
56
147
 
@@ -64,79 +155,84 @@ module Ruote
64
155
 
65
156
  if tracker_id == 'on_error' || tracker_id == 'on_terminate'
66
157
 
67
- fields = msg['workitem']['fields']
158
+ fs = msg['workitem']['fields']
68
159
 
69
- next if m_action == 'error_intercepted' && fields['__error__']
70
- next if m_action == 'terminated' && (fields['__error__'] || fields['__terminate__'])
160
+ next if m_action == 'error_intercepted' && fs['__error__']
161
+ next if m_action == 'terminated' && (fs['__error__'] || fs['__terminate__'])
71
162
  end
72
163
 
73
- # prepare and emit/put 'reaction' message
164
+ # remove the message post-trigger?
74
165
 
75
- m = Ruote.fulldup(tracker['msg'])
166
+ ids_to_remove << tracker_id if tracker['msg'].delete('_auto_remove')
76
167
 
77
- action = m.delete('action')
168
+ # OK, have to pull the trigger (or alter the message) then
78
169
 
79
- m['wfid'] = m_wfid if m['wfid'] == 'replace'
80
- m['wfid'] ||= @context.wfidgen.generate
81
-
82
- m['workitem'] = msg['workitem'] if m['workitem'] == 'replace'
83
-
84
- if t_action == 'error_intercepted'
85
- m['workitem']['fields']['__error__'] = m_error
86
- elsif tracker_id == 'on_error' && m_action == 'error_intercepted'
87
- m['workitem']['fields']['__error__'] = m_error
88
- elsif tracker_id == 'on_terminate' && m_action == 'terminated'
89
- m['workitem']['fields']['__terminate__'] = { 'wfid' => m_wfid }
170
+ if pre && tracker['msg']['_alter']
171
+ alter(m_wfid, m_error, m_action, msg, tracker)
172
+ else
173
+ trigger(m_wfid, m_error, m_action, msg, tracker)
90
174
  end
175
+ end
91
176
 
92
- if m['variables'] == 'compile'
93
- fexp = Ruote::Exp::FlowExpression.fetch(@context, msg['fei'])
94
- m['variables'] = fexp ? fexp.compile_variables : {}
95
- end
177
+ remove(ids_to_remove, nil)
178
+ end
96
179
 
97
- @context.storage.put_msg(action, m)
180
+ # Alters the msg, only called in "pre" mode.
181
+ #
182
+ def alter(m_wfid, m_error, m_action, msg, tracker)
183
+
184
+ case tracker['msg'].delete('_alter')
185
+ when 'merge' then msg.merge!(tracker['msg'])
186
+ #else ...
98
187
  end
99
188
  end
100
189
 
101
- # Adds a tracker (usually when a 'listen' expression gets applied).
190
+ # Prepares the message that gets placed on the ruote msg queue.
102
191
  #
103
- def add_tracker(wfid, action, id, conditions, msg)
192
+ def trigger(m_wfid, m_error, m_action, msg, tracker)
104
193
 
105
- conditions =
106
- conditions && conditions.remap { |(k, v), h| h[k] = Array(v) }
194
+ t_action = tracker['action']
195
+ tracker_id = tracker['id']
107
196
 
108
- doc = @context.storage.get_trackers
197
+ m = Ruote.fulldup(tracker['msg'])
109
198
 
110
- doc['trackers'][id] =
111
- { 'wfid' => wfid,
112
- 'action' => action,
113
- 'id' => id,
114
- 'conditions' => conditions,
115
- 'msg' => msg }
199
+ action = m.delete('action')
116
200
 
117
- r = @context.storage.put(doc)
201
+ m['wfid'] = m_wfid if m['wfid'] == 'replace'
202
+ m['wfid'] ||= @context.wfidgen.generate
118
203
 
119
- add_tracker(wfid, action, id, conditions, msg) if r
120
- # the put failed, have to redo the work
121
- end
204
+ m['workitem'] = msg['workitem'] if m['workitem'] == 'replace'
122
205
 
123
- # Removes a tracker (usually when a 'listen' expression replies to its
124
- # parent expression or is cancelled).
125
- #
126
- def remove_tracker(fei, doc=nil)
206
+ if t_action == 'error_intercepted'
207
+ m['workitem']['fields']['__error__'] = m_error
208
+ elsif tracker_id == 'on_error' && m_action == 'error_intercepted'
209
+ m['workitem']['fields']['__error__'] = m_error
210
+ elsif tracker_id == 'on_terminate' && m_action == 'terminated'
211
+ m['workitem']['fields']['__terminate__'] = { 'wfid' => m_wfid }
212
+ end
127
213
 
128
- doc ||= @context.storage.get_trackers
214
+ if m['variables'] == 'compile'
215
+ fexp = Ruote::Exp::FlowExpression.fetch(@context, msg['fei'])
216
+ m['variables'] = fexp ? fexp.compile_variables : {}
217
+ end
129
218
 
130
- doc['trackers'].delete(Ruote.to_storage_id(fei))
219
+ @context.storage.put_msg(action, m)
220
+ end
131
221
 
132
- r = @context.storage.put(doc)
222
+ # Returns the trackers currently registered.
223
+ #
224
+ # Note: this is called from on_pre_msg and on_msg, hence two times
225
+ # for a single msg. We trust the storage implementation to cache it
226
+ # for us.
227
+ #
228
+ def trackers
133
229
 
134
- remove_tracker(fei, r) if r
135
- # the put failed, have to redo the work
230
+ @context.storage.get_trackers['trackers']
136
231
  end
137
232
 
138
- protected
139
-
233
+ # Given a msg and a hash of conditions, returns true if the msg
234
+ # matches the conditions.
235
+ #
140
236
  def does_match?(msg, conditions)
141
237
 
142
238
  return true unless conditions
@@ -151,12 +247,14 @@ module Ruote
151
247
  vv = Ruote.regex_or_s(vv)
152
248
 
153
249
  val = case k
250
+
154
251
  when 'class' then msg['error']['class']
155
252
  when 'message' then msg['error']['message']
156
- else msg[k]
253
+
254
+ else Ruote.lookup(msg, k)
157
255
  end
158
256
 
159
- val && (vv.is_a?(String) ? (vv == val) : vv.match(val))
257
+ val && (vv.is_a?(Regexp) ? vv.match(val) : vv == val)
160
258
  end
161
259
  end
162
260
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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
@@ -111,6 +111,7 @@ module Ruote
111
111
  # ensure that all keys are strings
112
112
 
113
113
  unless k.is_a?(String)
114
+
114
115
  coll.delete(k)
115
116
  k = k.to_s
116
117
  coll[k] = v
@@ -125,8 +126,9 @@ module Ruote
125
126
  else
126
127
  block.call(coll, k, v)
127
128
  end
129
+ end
128
130
 
129
- elsif v.is_a?(Array) || v.is_a?(Hash)
131
+ if v.is_a?(Array) || v.is_a?(Hash)
130
132
 
131
133
  deep_mutate(v, keys, coll, &block)
132
134
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, 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
@@ -200,5 +200,55 @@ module Ruote
200
200
 
201
201
  o.is_a?(Array) ? o : o.to_s.split(/\s*,\s*/).collect { |e| e.strip }
202
202
  end
203
+
204
+ # A bit like #inspect but produces a tighter output (ambiguous to machines).
205
+ #
206
+ def self.insp(o, opts={})
207
+
208
+ case o
209
+ when nil
210
+ 'nil'
211
+ when Hash
212
+ trim = opts[:trim] || []
213
+ '{' +
214
+ o.reject { |k, v|
215
+ v.nil? && trim.include?(k.to_s)
216
+ }.collect { |k, v|
217
+ "#{k}: #{insp(v)}"
218
+ }.join(', ') +
219
+ '}'
220
+ when Array
221
+ '[' + o.collect { |e| insp(e) }.join(', ') + ']'
222
+ when String
223
+ o.match(/\s/) ? o.inspect : o
224
+ else
225
+ o.inspect
226
+ end
227
+ end
228
+
229
+ def self.pps(o, w=79)
230
+
231
+ PP.pp(o, StringIO.new, w).string
232
+ end
233
+
234
+ #--
235
+ # [de]camelize
236
+ #++
237
+
238
+ # Our own quick camelize implementation (no need to require active support).
239
+ #
240
+ def self.camelize(s, first_up=false)
241
+
242
+ s = s.capitalize if first_up
243
+
244
+ s.gsub(/(_.)/) { |x| x[1, 1].upcase }
245
+ end
246
+
247
+ # Quick decamelize implementation.
248
+ #
249
+ def self.decamelize(s)
250
+
251
+ s.gsub(/(.)([A-Z])/, '\1_\2').downcase
252
+ end
203
253
  end
204
254