poro 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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