poro 0.1.2 → 0.1.3

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.
@@ -160,14 +160,22 @@ mailto:jeff@paploo.net
160
160
 
161
161
  = Version History
162
162
 
163
- [0.1.2 - 2010-Sep-30] Feature Additions
163
+ [0.1.3 - 2010-Oct-21] Callbacks and MongoContext Bug Fixes.
164
+ * Added callbacks for common events.
165
+ * MongoContext: You can actually remove records now.
166
+ * MongoContext: Bignum encodes instead of throwing errors.
167
+ * MongoContext: Made recognition of true/false/nil class
168
+ during encoding more robust.
169
+ [0.1.2 - 2010-Sep-30] Feature Additions.
164
170
  * Added a module namespace factory.
165
- * HashContext: Find one is faster when the conditions restrict on the primary key.
171
+ * HashContext: Find one is faster when the conditions
172
+ restrict on the primary key.
166
173
  * Many HashContext bugs fixed.
167
174
  [0.1.1 - 2010-Sep-24] Minor Additions and Bug Fixes.
168
175
  * MongoContext now can optionally encode Symbols as hashes
169
176
  or just leave them as strings.
170
- * MongoContext can have conversion to BSON::ObjectId turned off.
177
+ * MongoContext can have conversion to BSON::ObjectId
178
+ turned off.
171
179
  * MongoContext can save Sets in various formats.
172
180
  * MongoContext handles namespaced models better.
173
181
  * Context doesn't error when trying to find by id.
@@ -176,7 +184,8 @@ mailto:jeff@paploo.net
176
184
  to big changes as it is used in the real world.
177
185
  * Only supports MongoDB and Hash Contexts.
178
186
  * No performance testing and optimization yet done.
179
- * The documentation is rough around the edges and may contain errors.
187
+ * The documentation is rough around the edges and may
188
+ contain errors.
180
189
  * Spec tests are incomplete.
181
190
 
182
191
  = TODO List
@@ -184,26 +193,17 @@ mailto:jeff@paploo.net
184
193
  The following are the primary TODO items, roughly in priority order:
185
194
 
186
195
  * YAML Connection Configuration:
187
- * Make a Util module that is able to use a rails-compatible YAML
196
+ * Make a Util module that is able to use a rails-style YAML
188
197
  file--given by path--to get the elements needed for configuration of a
189
198
  SingleStore factory.
190
199
  * Modify SingleStore to use this file for configuration when appropriate.
191
200
  * Modelify: Break into modules for each piece of functionality.
192
- * Modelify: Add callback functionality to Modelify (e.g. before/after save, after initialize).
193
- * Add the callbacks to the Context first.
194
- * Mongo Context: Add option to encode Sets as one of:
195
- * A Set with the raw internal Hash.
196
- * A Set with the internal Hash as an Array.
197
- * An Array (which will have to be manually turned back into a Set).
198
201
  * Specs: Add specs for Context Find methods.
199
- * Check that private methods are private. (Should do on subclasses too.)
200
- * Check that the two main find methods pass through to the correct underlying
201
- methods or throw an argument when necessary.
202
202
  * Specs: Add spec tests for Mongo Context.
203
203
  * Mongo Context: Split into modules in separate files.
204
204
  * Context: Split out modules into files.
205
205
  * Contexts: Add SQL Context.
206
- * Ruby: Verify support for ruby 1.9.x.
206
+ * Ruby: Verify support for ruby 1.9.0 and 1.9.1.
207
207
  * Ruby: Evaluate adding support for ruby 1.8.6 and 1.8.7.
208
208
 
209
209
  = License
@@ -240,5 +240,5 @@ GPL compatible "New BSD License", given below:
240
240
 
241
241
  Poro::Util::Inflector and its submodules are adapted from ActiveSupport,
242
242
  and its source is redistributed under the MIT license it was originally
243
- distributed under. Thetext of this copyright notice is supplied
243
+ distributed under. The text of this copyright notice is supplied
244
244
  in <tt>poro/util/inflector.rb</tt>.
@@ -111,14 +111,16 @@ module Poro
111
111
  # Fetches the object from the store with the given id, or returns nil
112
112
  # if there are none matching.
113
113
  def fetch(id)
114
- return convert_to_plain_object( clean_id(nil) )
114
+ obj = convert_to_plain_object( clean_id(nil) )
115
+ callback_event(:after_fetch, obj)
116
+ return obj
115
117
  end
116
118
 
117
119
  # Saves the given object to the persistent store using this context.
118
120
  #
119
121
  # Subclasses do not need to call super, but should follow the given rules:
120
122
  #
121
- # Returns self so that calls may be daisy chained.
123
+ # Returns the saved object.
122
124
  #
123
125
  # If the object has never been saved, it should be inserted and given
124
126
  # an id. If the object has been added before, the id is used to update
@@ -126,7 +128,9 @@ module Poro
126
128
  #
127
129
  # Raises an Error if save fails.
128
130
  def save(obj)
131
+ callback_event(:before_save, obj)
129
132
  obj.id = obj.object_id if obj.respond_to?(:id) && obj.id.nil? && obj.respond_to?(:id=)
133
+ callback_event(:after_save, obj)
130
134
  return obj
131
135
  end
132
136
 
@@ -134,13 +138,15 @@ module Poro
134
138
  #
135
139
  # Subclasses do not need to call super, but should follow the given rules:
136
140
  #
137
- # Returns self so that calls may be daisy chained.
141
+ # Returns the removed object.
138
142
  #
139
143
  # If the object is successfully removed, the id is set to nil.
140
144
  #
141
145
  # Raises an Error is the remove fails.
142
146
  def remove(obj)
147
+ callback_event(:before_remove, obj)
143
148
  obj.id = nil if obj.respond_to?(:id=)
149
+ callback_event(:after_remove, obj)
144
150
  return obj
145
151
  end
146
152
 
@@ -157,7 +163,10 @@ module Poro
157
163
  # Any root object returned from a "find" in the data store needs to be
158
164
  # able to be converted
159
165
  def convert_to_plain_object(data, state_info={})
160
- return data
166
+ transformed_data = callback_transform(:before_convert_to_plain_object, data)
167
+ obj = transformed_data
168
+ callback_event(:after_convert_to_plain_object, obj)
169
+ return obj
161
170
  end
162
171
 
163
172
  # Convert a plain ol' ruby object into the data store data format this
@@ -173,7 +182,10 @@ module Poro
173
182
  # Any root object returned from a "find" in the data store needs to be
174
183
  # able to be converted
175
184
  def convert_to_data(obj, state_info={})
176
- return obj
185
+ transformed_obj = callback_transform(:before_convert_to_data, obj)
186
+ data = transformed_obj
187
+ callback_event(:after_convert_to_data, data)
188
+ return data
177
189
  end
178
190
 
179
191
  private
@@ -457,8 +469,135 @@ module Poro
457
469
  end
458
470
  end
459
471
 
472
+ module Poro
473
+ class Context
474
+ # A mixin to support callbacks. There are three kinds of callbacks:
475
+ # [Events] Events are callbacks that are passed a handle to the object when
476
+ # a particular kind of event has occured. These may destructively
477
+ # edit objects.
478
+ # [Transform] Transforms are callbacks where each handler is passed the
479
+ # result of the previous transform, and may return any value.
480
+ # The issuing object then uses the final value in some way.
481
+ # [Filters] Calls each callback in sequence, pasing in the issuing object.
482
+ # Terminates execution on the first callback that is "false" (as
483
+ # determined by an if statement), or when there are no callbacks
484
+ # left. Gives the issuing object the result of the last block.
485
+ #
486
+ # Contexts issue the following event callbacks:
487
+ # [:before_save] Called before save; passes the object that is going to be saved.
488
+ # [:after_save] Called after save; passes the object that was saved.
489
+ # [:before_remove] Called before removing an object from persistent storage; passes the object that will be removed.
490
+ # [:after_remove] Called after removing an object from persistent storage; passes the object that was removed.
491
+ # [:after_fech] Called after an object is fetched from the persistent store; passes the object that was fetched.
492
+ # [:after_convert_to_plain_object] Called after an object is converted to a plain object from the persistent store but before it is used; passes the plain object.
493
+ # [:after_convert_to_data] Called after an object is converted to the persistent store's data structure but before it is used; passes the data store's data structure.
494
+ #
495
+ # Contexts issue the following transform callbacks:
496
+ #
497
+ # [:before_convert_to_plain_object] Called just before a context converts
498
+ # persistent store data to a plain ruby object;
499
+ # is passed the persistent store data object;
500
+ # the result is what is converted.
501
+ #
502
+ # In most cases it is better to use the
503
+ # +after_convert_to_plain_object+ callback event.
504
+ # [:before_convert_to_data] Called just before a context converts
505
+ # a plain ruby object to persistent store data;
506
+ # is passed the plain ruby object;
507
+ # the result is what is converted.
508
+ #
509
+ # In most cases it is better to use the
510
+ # +before_convert_to_plain_object+ callback event.
511
+ module CallbackMethods
512
+
513
+ # Return the raw array of callbacks. This can be manipulated if more
514
+ # straightforward methods don't do the trick, but usually this is
515
+ # a consequence of trying to solve the problem wrong.
516
+ #
517
+ # While usually a kind of Proc, callbacks may be any object that responds
518
+ # to call.
519
+ def callbacks(event)
520
+ @event_callbacks ||= {}
521
+ key = event.to_sym
522
+ @event_callbacks[key] ||= []
523
+ return @event_callbacks[key]
524
+ end
525
+
526
+ # Register a callback for a given event.
527
+ def register_callback(event, &block)
528
+ callbacks(event) << block
529
+ end
530
+
531
+ # Clear all callbacks for a given event.
532
+ #
533
+ # This can be dangerous because
534
+ def clear_callbacks(event)
535
+ callbacks(event).clear
536
+ end
537
+
538
+ private
539
+
540
+ # Fires the callbacks for the given event; returns the object supplied
541
+ # for calling.
542
+ #
543
+ # * Each registered callback is given the object issued with the call.
544
+ # * Depending on your uses, the callback may be destructive of the passed object.
545
+ # * The callback returns are ignored.
546
+ #
547
+ # Registration of no callbacks results in no callbacks being called.
548
+ def callback_event(event, obj)
549
+ callbacks(event).each {|callback| callback.call(obj)}
550
+ return obj
551
+ end
552
+
553
+ # Transforms an object through a callback chain; returns the transformed
554
+ # object.
555
+ #
556
+ # * Each registered callback is given the result of the previous callback.
557
+ # * Callbacks may return the original object (modified or unmodified), a
558
+ # copy of the original object (modified or unmodified), or an entirely
559
+ # new object, depending on how the result is used.
560
+ # * The callback return is passed into the next callback, with the last
561
+ # return being called to the initial caller.
562
+ #
563
+ # Registration of no callbacks results in the return of the original object.
564
+ def callback_transform(event, initial_obj)
565
+ return callbacks(event).inject(initial_obj) {|obj, callback| callback.call(obj)}
566
+ end
567
+
568
+ # Executes callbacks until the last true-valued filter; returns the last
569
+ # true valued object.
570
+ #
571
+ # By convention, filter events should end in a question mark to make it
572
+ # clear that the true/false value is important.
573
+ #
574
+ # * Each registered callback is given the original object, making this
575
+ # behave more like an event than a transform.
576
+ # * Filters are expected to be non-destructive, as they are used to
577
+ # determine if an action should take place, rather than to take an
578
+ # action.
579
+ # * If the return of a callback is false-values (as determined by an +if+
580
+ # expression), then the filter chain is halted and the value is returned;
581
+ # otherwise, the value returned from the last callback is returned.
582
+ #
583
+ # Registration of no callbacks results in the return of the +default_value+
584
+ # argument, which--if not provided--is set to true.
585
+ def callback_filter?(event, obj, default_result=true)
586
+ result = default_result
587
+ callbacks(event).each do |callback|
588
+ result = callback.call(obj)
589
+ break unless result
590
+ end
591
+ return result
592
+ end
593
+
594
+ end
595
+ end
596
+ end
597
+
460
598
  module Poro
461
599
  class Context
462
600
  include FindMethods
601
+ include CallbackMethods
463
602
  end
464
603
  end
@@ -12,11 +12,15 @@ module Poro
12
12
  end
13
13
 
14
14
  def fetch(id)
15
- return convert_to_plain_object( data_store[clean_id(id)] )
15
+ obj = convert_to_plain_object( data_store[clean_id(id)] )
16
+ callback_event(:after_fetch, obj)
17
+ return obj
16
18
  end
17
19
 
18
20
  # Save the object in the underlying hash, using the object id as the key.
19
21
  def save(obj)
22
+ callback_event(:before_save, obj)
23
+
20
24
  pk_id = self.primary_key_value(obj)
21
25
  if(pk_id.nil?)
22
26
  pk_id = obj.object_id
@@ -24,25 +28,37 @@ module Poro
24
28
  end
25
29
 
26
30
  data_store[pk_id] = convert_to_data(obj)
27
- return self
31
+
32
+ callback_event(:after_save, obj)
33
+ return obj
28
34
  end
29
35
 
30
36
  # Remove the object from the underlying hash.
31
37
  def remove(obj)
38
+ callback_event(:before_remove, obj)
39
+
32
40
  pk_id = self.primary_key_value(obj)
33
41
  if( pk_id != nil )
34
42
  data_store.delete(pk_id)
35
43
  self.set_primary_key_value(obj, nil)
36
44
  end
37
- return self
45
+
46
+ callback_event(:after_remove, obj)
47
+ return obj
38
48
  end
39
49
 
40
50
  def convert_to_plain_object(data)
41
- return data
51
+ transformed_data = callback_transform(:before_convert_to_plain_object, data)
52
+ obj = transformed_data
53
+ callback_event(:after_convert_to_plain_object, obj)
54
+ return obj
42
55
  end
43
56
 
44
57
  def convert_to_data(obj)
45
- return obj
58
+ transformed_obj = callback_transform(:before_convert_to_data, obj)
59
+ data = transformed_obj
60
+ callback_event(:after_convert_to_data, data)
61
+ return data
46
62
  end
47
63
 
48
64
  private
@@ -91,29 +91,44 @@ module Poro
91
91
 
92
92
  def fetch(id)
93
93
  data = data_store.find_one( clean_id(id) )
94
- return convert_to_plain_object(data)
94
+ obj convert_to_plain_object(data)
95
+ callback_event(:before_fetch, obj)
96
+ return obj
95
97
  end
96
98
 
97
99
  def save(obj)
100
+ callback_event(:before_save, obj)
98
101
  data = convert_to_data(obj)
99
102
  data_store.save(data)
100
103
  set_primary_key_value(obj, (data['_id'] || data[:_id])) # The pk generator uses a symbol, while everything else uses a string!
104
+ callback_event(:after_save, obj)
101
105
  return obj
102
106
  end
103
107
 
104
108
  def remove(obj)
109
+ callback_event(:before_remove, obj)
110
+ data_store.remove( {'_id' => primary_key_value(obj)} )
111
+ callback_event(:after_remove, obj)
105
112
  return obj
106
113
  end
107
114
 
108
115
  def convert_to_plain_object(data, state_info={})
116
+ transformed_data = callback_transform(:before_convert_to_plain_object, data)
117
+
109
118
  # If it is a root record, and it has no class name, assume this context's class name.
110
- data['_class_name'] = self.klass if( data && data.kind_of?(Hash) && !state_info[:embedded] )
111
- obj = route_decode(data, state_info)
119
+ transformed_data['_class_name'] = self.klass if( transformed_data && transformed_data.kind_of?(Hash) && !state_info[:embedded] )
120
+ obj = route_decode(transformed_data, state_info)
121
+
122
+ callback_event(:after_convert_to_plain_object, obj)
112
123
  return obj
113
124
  end
114
125
 
115
126
  def convert_to_data(obj, state_info={})
116
- data = route_encode(obj, state_info)
127
+ transformed_obj = callback_transform(:before_convert_to_data, obj)
128
+
129
+ data = route_encode(transformed_obj, state_info)
130
+
131
+ callback_event(:after_convert_to_data, data)
117
132
  return data
118
133
  end
119
134
 
@@ -150,9 +165,9 @@ module Poro
150
165
  obj.kind_of?(String) ||
151
166
  obj.kind_of?(Time) ||
152
167
  (!self.encode_symbols && obj.kind_of?(Symbol)) ||
153
- obj==true ||
154
- obj==false ||
155
- obj.nil? ||
168
+ obj.kind_of?(TrueClass) ||
169
+ obj.kind_of?(FalseClass) ||
170
+ obj.kind_of?(NilClass) ||
156
171
  obj.kind_of?(BSON::ObjectId) ||
157
172
  obj.kind_of?(BSON::DBRef)
158
173
  )
@@ -205,6 +220,8 @@ module Poro
205
220
  return encode_array(obj)
206
221
  elsif( obj.kind_of?(Class) )
207
222
  return encode_class(obj)
223
+ elsif( obj.kind_of?(Bignum) )
224
+ return encode_bigint(obj)
208
225
  elsif( obj.kind_of?(Set) )
209
226
  return encode_set(obj)
210
227
  elsif( self.encode_symbols && obj.kind_of?(Symbol) )
@@ -243,6 +260,11 @@ module Poro
243
260
  return {'_class_name' => 'Symbol', 'value' => sym.to_s}
244
261
  end
245
262
 
263
+ # Encodes a big-int, which is too big to be natively encoded in BSON.
264
+ def encode_bigint(bigint)
265
+ return {'_class_name' => 'Bignum', 'value' => bigint.to_s}
266
+ end
267
+
246
268
  # Encodes a Set as either :raw, :embedded_array, :array.
247
269
  def encode_set(set)
248
270
  method = @set_encoding_method
@@ -344,6 +366,8 @@ module Poro
344
366
  return decode_class(data)
345
367
  elsif( class_name == 'Symbol' )
346
368
  return decode_symbol(data)
369
+ elsif( class_name == 'Bignum' )
370
+ return decode_bigint(data)
347
371
  elsif( class_name == 'Set' )
348
372
  return decode_set(data)
349
373
  elsif( class_name == self.klass.to_s )
@@ -384,6 +408,11 @@ module Poro
384
408
  end
385
409
  end
386
410
 
411
+ # Decode an encoded bigint.
412
+ def decode_bigint(bigint_data)
413
+ return bigint_data['value'].to_i
414
+ end
415
+
387
416
  # Decode the set depending on if it was encoded as an array or as a raw
388
417
  # object.
389
418
  def decode_set(set_data)
@@ -3,5 +3,5 @@
3
3
  # existing plain ol' ruby objects as little as possible. For more information
4
4
  # see README.rdoc.
5
5
  module Poro
6
- VERSION = '0.1.2'
6
+ VERSION = '0.1.3'
7
7
  end
@@ -107,4 +107,129 @@ describe "Context" do
107
107
  Poro::Context.fetch(@klass_one.new).should == "#{@klass_one}, #{x}"
108
108
  end
109
109
 
110
+ describe 'Callbakcs' do
111
+
112
+ before(:each) do
113
+ @context = @context_klass.new(@klass_one)
114
+ end
115
+
116
+ it 'should allow direct inspection of callbacks' do
117
+ bs_callbacks = @context.callbacks(:before_save)
118
+ bs_callbacks.should be_kind_of(Array)
119
+
120
+ as_callbacks = @context.callbacks(:after_save)
121
+ as_callbacks.should be_kind_of(Array)
122
+
123
+ as_callbacks.object_id.should_not == bs_callbacks.object_id
124
+ @context.callbacks(:before_save).object_id.should == bs_callbacks.object_id
125
+ end
126
+
127
+ it 'should allow callback registration' do
128
+ @context.callbacks(:before_save).should be_empty
129
+ @context.register_callback(:before_save) {|obj| obj}
130
+ @context.callbacks(:before_save).length.should == 1
131
+
132
+ @context.register_callback(:after_save) {|obj| obj}
133
+ @context.callbacks(:after_save).length.should == 1
134
+
135
+ @context.callbacks(:before_save).length.should == 1
136
+ end
137
+
138
+ it 'should clear callbacks' do
139
+ @context.register_callback(:before_save) {|obj| obj}
140
+ @context.callbacks(:before_save).length.should == 1
141
+ @context.clear_callbacks(:before_save)
142
+ @context.callbacks(:before_save).should be_empty
143
+ end
144
+
145
+ it 'should have private firing methods' do
146
+ @context.private_methods.should include(:callback_event)
147
+ @context.private_methods.should include(:callback_transform)
148
+ @context.private_methods.should include(:callback_filter?)
149
+ end
150
+
151
+ it 'should call event callbacks' do
152
+ @context.register_callback(:before_save) {|obj| obj[:foo] = 'bar'}
153
+ @context.register_callback(:before_save) {|obj| obj[:alpha] = 'beta'}
154
+ @context.register_callback(:after_save) {|obj| obj[:p] = 'q'}
155
+
156
+ some_object = {:foo => 'untouched', :value => 12345}
157
+ result = @context.send(:callback_event, :before_save, some_object)
158
+ result.object_id.should == some_object.object_id
159
+ some_object.should == {:foo => 'bar', :value => 12345, :alpha => 'beta'}
160
+ end
161
+
162
+ it 'should handle transform callbacks' do
163
+ @context.register_callback(:before_save) {|obj| obj.merge(:foo => 'bar')}
164
+ @context.register_callback(:before_save) {|obj| obj.merge(:alpha => 'beta').to_a}
165
+ @context.register_callback(:after_save) {|obj| 'q'}
166
+
167
+ some_object = {:foo => 'untouched', :value => 12345}
168
+ result = @context.send(:callback_transform, :before_save, some_object)
169
+ result.should == [[:foo, 'bar'], [:value, 12345], [:alpha, 'beta']]
170
+ some_object.should == {:foo => 'untouched', :value => 12345}
171
+ end
172
+
173
+ it 'should handle no transform callbacks' do
174
+ @context.callbacks(:before_save).should be_empty
175
+
176
+ some_object = {:foo => 'untouched', :value => 12345}
177
+ result = @context.send(:callback_transform, :before_save, some_object)
178
+ result.object_id.should == some_object.object_id
179
+ end
180
+
181
+ it 'should handle filter callbacks' do
182
+ @context.register_callback(:should_save?) {|obj| obj[:foo] = 'bar'; nil}
183
+ @context.register_callback(:should_save?) {|obj| obj[:alpha] = 'beta'; obj}
184
+ @context.register_callback(:should_remove?) {|obj| obj[:p] = 'q'; :done}
185
+
186
+ # Make sure it cancels properly.
187
+ some_object = {:foo => 'untouched', :value => 12345}
188
+ result = @context.send(:callback_filter?, :should_save?, some_object)
189
+ result.should be_nil
190
+ some_object.should == {:foo => 'bar', :value => 12345}
191
+
192
+ # Make sure it still runs properly, even if the default is false.
193
+ some_object = {:foo => 'untouched', :value => 12345}
194
+ result = @context.send(:callback_filter?, :should_save?, some_object, false)
195
+ result.should be_nil
196
+ some_object.should == {:foo => 'bar', :value => 12345}
197
+
198
+ # Make sure it falls off the end correctly.
199
+ some_object = {:foo => 'untouched', :value => 12345}
200
+ result = @context.send(:callback_filter?, :should_remove?, some_object)
201
+ result.should == :done
202
+ some_object.should == {:foo => 'untouched', :value => 12345, :p => 'q'}
203
+ end
204
+
205
+ it 'should handle no filter callbacks' do
206
+ @context.callbacks(:save_should?).should be_empty
207
+
208
+ # Make sure it defaults to true when there are no callbacks.
209
+ some_object = {:foo => 'untouched', :value => 12345}
210
+ result = @context.send(:callback_filter?, :should_save?, some_object)
211
+ result.should == true
212
+ some_object.should == {:foo => 'untouched', :value => 12345}
213
+
214
+ # Make sure it uses the passed default when there are no callbacks.
215
+ some_object = {:foo => 'untouched', :value => 12345}
216
+ result = @context.send(:callback_filter?, :should_save?, some_object, :some_default)
217
+ result.should == :some_default
218
+ some_object.should == {:foo => 'untouched', :value => 12345}
219
+ end
220
+
221
+ end
222
+
223
+ describe 'FindHelpers' do
224
+
225
+ it 'should have base methods private' do
226
+ pending
227
+ end
228
+
229
+ it 'should pass calls from the main two public methods to their underlying private methods based on argument' do
230
+ pending
231
+ end
232
+
233
+ end
234
+
110
235
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 2
9
- version: 0.1.2
8
+ - 3
9
+ version: 0.1.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jeff Reinecke
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-09-30 00:00:00 -07:00
17
+ date: 2010-10-21 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies: []
20
20