picky 2.2.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,14 +13,16 @@
13
13
  # will generate an example <tt>project_name/app/application.rb</tt> file for you
14
14
  # with some example code inside.
15
15
  #
16
- # == Index::Memory.new(name, source)
16
+ # == Index::Memory.new(name)
17
17
  #
18
18
  # Next, define where your data comes from. You use the <tt>Index::Memory.new</tt> method for that:
19
- # my_index = Index::Memory.new :some_index_name, some_source
19
+ # my_index = Index::Memory.new :some_index_name
20
20
  # You give the index a name (or identifier), and a source (see Sources), where its data comes from. Let's do that:
21
21
  # class MyGreatSearch < Application
22
22
  #
23
- # books = Index::Memory.new :books, Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv')
23
+ # books = Index::Memory.new :books do
24
+ # source Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv')
25
+ # end
24
26
  #
25
27
  # end
26
28
  # Now we have an index <tt>books</tt>.
@@ -29,7 +31,7 @@
29
31
  #
30
32
  # Note that a Redis index is also available: Index::Redis.new.
31
33
  #
32
- # == index_instance.define_category(identifier, options = {})
34
+ # == category(identifier, options = {})
33
35
  #
34
36
  # Picky needs us to define categories on the data.
35
37
  #
@@ -39,8 +41,10 @@
39
41
  # Let's go ahead and define a category:
40
42
  # class MyGreatSearch < Application
41
43
  #
42
- # books = Index::Memory.new :books, Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv')
43
- # books.define_category :title
44
+ # books = Index::Memory.new :books do
45
+ # source Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv')
46
+ # category :title
47
+ # end
44
48
  #
45
49
  # end
46
50
  # Now we could already run the indexer:
@@ -52,7 +56,7 @@
52
56
  #
53
57
  # == Search.new(*indexes, options = {})
54
58
  #
55
- # We need somebody who asks the index (a Query object, also see http://github.com/floere/picky/wiki/Queries-Configuration). That works like this:
59
+ # We need somebody who asks the index (a Query object, also see http://github.com/floere/picky/wiki/Queries-Configuration):
56
60
  # books_search = Search.new books
57
61
  #
58
62
  # Now we have somebody we can ask about the index. But no external interface.
@@ -64,8 +68,10 @@
64
68
  # In full glory:
65
69
  # class MyGreatSearch < Application
66
70
  #
67
- # books = index :books, Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv')
68
- # books.define_category :title
71
+ # books = index :books do
72
+ # source Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv')
73
+ # category :title
74
+ # end
69
75
  #
70
76
  # route %r{^/books$} => Search.new(books)
71
77
  #
@@ -82,19 +88,19 @@
82
88
  #
83
89
  # Maybe you don't find everything. We need to process the data before it goes into the index.
84
90
  #
85
- # == default_indexing(options = {})
91
+ # == indexing(options = {})
86
92
  #
87
- # That's what the <tt>default_indexing</tt> method is for:
88
- # default_indexing options
93
+ # That's what the <tt>indexing</tt> method is for:
94
+ # indexing options
89
95
  # Read more about the options here: http://github.com/floere/picky/wiki/Indexing-configuration
90
96
  #
91
97
  # Same thing with the search text – we need to process that as well.
92
98
  #
93
- # == default_querying(options = {})
99
+ # == searching(options = {})
94
100
  #
95
- # Analog to the default_indexing method, we use the <tt>default_querying</tt> method.
96
- # default_querying options
97
- # Read more about the options here: http://github.com/floere/picky/wiki/Querying-Configuration
101
+ # Analog to the indexing method, we use the <tt>searching</tt> method.
102
+ # searching options
103
+ # Read more about the options here: http://github.com/floere/picky/wiki/Searching-Configuration
98
104
  #
99
105
  # And that's all there is. It's incredibly powerful though, as you can combine, weigh, refine to the max.
100
106
  #
@@ -109,35 +115,37 @@
109
115
  # Our example, fully fleshed out with indexing, querying, and weights:
110
116
  # class MyGreatSearch < Application
111
117
  #
112
- # default_indexing removes_characters: /[^a-zA-Z0-9\.]/,
113
- # stopwords: /\b(and|or|in|on|is|has)\b/,
114
- # splits_text_on: /\s/,
115
- # removes_characters_after_splitting: /\./,
116
- # substitutes_characters_with: CharacterSubstituters::WestEuropean.new,
117
- # normalizes_words: [
118
- # [/(.*)hausen/, 'hn'],
119
- # [/\b(\w*)str(eet)?/, 'st']
120
- # ]
121
- #
122
- # default_querying removes_characters: /[^a-zA-Z0-9\s\/\-\,\&\"\~\*\:]/,
123
- # stopwords: /\b(and|the|of|it|in|for)\b/,
124
- # splits_text_on: /[\s\/\-\,\&]+/,
125
- # removes_characters_after_splitting: /\./,
126
- # substitutes_characters_with: CharacterSubstituters::WestEuropean.new,
127
- # maximum_tokens: 4
128
- #
129
- # books = Index::Memory.new :books, Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv')
130
- # books.define_category :title,
131
- # qualifiers: [:t, :title, :titre],
132
- # partial: Partial::Substring.new(:from => 1),
133
- # similarity: Similarity::DoubleMetaphone.new(2)
134
- # books.define_category :author,
135
- # partial: Partial::Substring.new(:from => -2)
136
- # books.define_category :isbn
137
- #
138
- # options = { :weights => { [:title, :author] => +3, [:author, :title] => -1 } }
139
- #
140
- # route %r{^/books$} => Search.new(books, options)
118
+ # indexing removes_characters: /[^a-zA-Z0-9\.]/,
119
+ # stopwords: /\b(and|or|in|on|is|has)\b/,
120
+ # splits_text_on: /\s/,
121
+ # removes_characters_after_splitting: /\./,
122
+ # substitutes_characters_with: CharacterSubstituters::WestEuropean.new,
123
+ # normalizes_words: [
124
+ # [/(.*)hausen/, 'hn'],
125
+ # [/\b(\w*)str(eet)?/, 'st']
126
+ # ]
127
+ #
128
+ # searching removes_characters: /[^a-zA-Z0-9\s\/\-\,\&\"\~\*\:]/,
129
+ # stopwords: /\b(and|the|of|it|in|for)\b/,
130
+ # splits_text_on: /[\s\/\-\,\&]+/,
131
+ # removes_characters_after_splitting: /\./,
132
+ # substitutes_characters_with: CharacterSubstituters::WestEuropean.new,
133
+ # maximum_tokens: 4
134
+ #
135
+ # books = Index::Memory.new :books do
136
+ # source Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv')
137
+ # category :title,
138
+ # qualifiers: [:t, :title, :titre],
139
+ # partial: Partial::Substring.new(:from => 1),
140
+ # similarity: Similarity::DoubleMetaphone.new(2)
141
+ # category :author,
142
+ # partial: Partial::Substring.new(:from => -2)
143
+ # category :isbn
144
+ # end
145
+ #
146
+ # route %r{^/books$} => Search.new(books) do
147
+ # boost [:title, :author] => +3, [:author, :title] => -1
148
+ # end
141
149
  #
142
150
  # end
143
151
  # That's actually already a full-blown Picky App!
@@ -15,15 +15,15 @@ module Index
15
15
  #
16
16
  # === Parameters
17
17
  # * name: A name that will be used for the index directory and in the Picky front end.
18
- # * source: Where the data comes from, e.g. Sources::CSV.new(...). Optional, can be defined in the block using #source.
19
18
  #
20
19
  # === Options
20
+ # * source: Where the data comes from, e.g. Sources::CSV.new(...). Optional, can be defined in the block using #source.
21
21
  # * result_identifier: Use if you'd like a different identifier/name in the results than the name of the index.
22
22
  # * after_indexing: As of this writing only used in the db source. Executes the given after_indexing as SQL after the indexing process.
23
- # * tokenizer: The tokenizer to use for this index.
23
+ # * tokenizer: The tokenizer to use for this index. Optional, can be defined in the block using #indexing.
24
24
  #
25
25
  # Examples:
26
- # my_index = Index::Memory.new(:my_index, some_source) do
26
+ # my_index = Index::Memory.new(:my_index, source: some_source) do
27
27
  # category :bla
28
28
  # end
29
29
  #
@@ -64,10 +64,10 @@ module Index
64
64
  raise ArgumentError.new(<<-NAME
65
65
 
66
66
 
67
- The index identifier (you gave "#{name}") for Index::Memory/Index::Redis should be a String/Symbol,
67
+ The index identifier (you gave "#{name}") for Index::Memory/Index::Redis should be a Symbol/String,
68
68
  Examples:
69
- Index::Memory.new(:my_cool_index, ...) # Recommended
70
- Index::Redis.new("a-redis-index", ...)
69
+ Index::Memory.new(:my_cool_index) # Recommended
70
+ Index::Redis.new("a-redis-index")
71
71
  NAME
72
72
 
73
73
 
@@ -121,12 +121,12 @@ INDEX
121
121
 
122
122
  # Define an index tokenizer on the index.
123
123
  #
124
- # Parameters are the exact same as for the default_indexing.
124
+ # Parameters are the exact same as for indexing.
125
125
  #
126
- def define_indexing options = {}
126
+ def indexing options = {}
127
127
  internal_indexing.define_indexing options
128
128
  end
129
- alias indexing define_indexing
129
+ alias define_indexing indexing
130
130
 
131
131
  # Define a source on the index.
132
132
  #
@@ -134,10 +134,10 @@ INDEX
134
134
  # anything responding to #each and returning objects that
135
135
  # respond to id and the category names (or the category from option).
136
136
  #
137
- def define_source source
137
+ def source source
138
138
  internal_indexing.define_source source
139
139
  end
140
- alias source define_source
140
+ alias define_source source
141
141
 
142
142
  # Defines a searchable category on the index.
143
143
  #
@@ -152,7 +152,7 @@ INDEX
152
152
  # * source: Use a different source than the index uses. If you think you need that, there might be a better solution to your problem. Please post to the mailing list first with your application.rb :)
153
153
  # * from: Take the data from the data category with this name. Example: You have a source Sources::CSV.new(:title, file:'some_file.csv') but you want the category to be called differently. The you use from: define_category(:similar_title, :from => :title).
154
154
  #
155
- def define_category category_name, options = {}
155
+ def category category_name, options = {}
156
156
  category_name = category_name.to_sym
157
157
 
158
158
  indexing_category = internal_indexing.define_category category_name, options
@@ -162,11 +162,10 @@ INDEX
162
162
 
163
163
  self
164
164
  end
165
- alias category define_category
165
+ alias define_category category
166
166
 
167
- # HIGHLY EXPERIMENTAL Try if you feel "beta" ;)
168
- #
169
- # Make this category range searchable with a fixed range. If you need other ranges, define another category with a different range value.
167
+ # Make this category range searchable with a fixed range. If you need other
168
+ # ranges, define another category with a different range value.
170
169
  #
171
170
  # Example:
172
171
  # You have data values inside 1..100, and you want to have Picky return
@@ -174,22 +173,27 @@ INDEX
174
173
  # 45, 46, or 47.2, 48.9, in a range of 2 around 47, so (45..49).
175
174
  #
176
175
  # Then you use:
177
- # my_index.define_ranged_category :values_inside_1_100, 2
176
+ # ranged_category :values_inside_1_100, 2
178
177
  #
179
178
  # Optionally, you give it a precision value to reduce the error margin
180
179
  # around 47 (Picky is a bit liberal).
181
- # my_index.define_ranged_category :values_inside_1_100, 2, precision: 5
180
+ # Index::Memory.new :range do
181
+ # ranged_category :values_inside_1_100, 2, precision: 5
182
+ # end
182
183
  #
183
184
  # This will force Picky to maximally be wrong 5% of the given range value
184
185
  # (5% of 2 = 0.1) instead of the default 20% (20% of 2 = 0.4).
185
186
  #
186
- # We suggest not to use much more than 5 as a higher precision is more performance intensive for less and less precision gain.
187
+ # We suggest not to use much more than 5 as a higher precision is more
188
+ # performance intensive for less and less precision gain.
187
189
  #
188
190
  # == Protip 1
189
191
  #
190
192
  # Create two ranged categories to make an area search:
191
- # index.define_ranged_category :x, 1
192
- # index.define_ranged_category :y, 1
193
+ # Index::Memory.new :area do
194
+ # ranged_category :x, 1
195
+ # ranged_category :y, 1
196
+ # end
193
197
  #
194
198
  # Search for it using for example:
195
199
  # x:133, y:120
@@ -223,7 +227,7 @@ INDEX
223
227
  # * precision: Default is 1 (20% error margin, very fast), up to 5 (5% error margin, slower) makes sense.
224
228
  # * ... all options of #define_category.
225
229
  #
226
- def define_ranged_category category_name, range, options = {}
230
+ def ranged_category category_name, range, options = {}
227
231
  precision = options[:precision] || 1
228
232
 
229
233
  options = { partial: Partial::None.new }.merge options
@@ -233,17 +237,18 @@ INDEX
233
237
  Internals::Indexed::Wrappers::Category::Location.install_on indexed_category, range, precision
234
238
  end
235
239
  end
236
- alias ranged_category define_ranged_category
240
+ alias define_ranged_category ranged_category
237
241
 
238
242
  # HIGHLY EXPERIMENTAL Not correctly working yet. Try it if you feel "beta".
239
243
  #
240
- # Also a range search see #define_ranged_category, but on the earth's surface.
244
+ # Also a range search see #ranged_category, but on the earth's surface.
241
245
  #
242
246
  # Parameters:
243
- # * name: The name as used in #define_category.
247
+ # * lat_name: The latitude's name as used in #define_category.
248
+ # * lng_name: The longitude's name as used in #define_category.
244
249
  # * radius: The distance (in km) around the query point which we search for results.
245
250
  #
246
- # Note: Picky uses a square, not a circle. We hope that's ok for most usages.
251
+ # Note: Picky uses a square, not a circle. That should be ok for most usages.
247
252
  #
248
253
  # -----------------------------
249
254
  # | |
@@ -261,23 +266,40 @@ INDEX
261
266
  #
262
267
  # Options
263
268
  # * precision: Default 1 (20% error margin, very fast), up to 5 (5% error margin, slower) makes sense.
264
- # * from: The data category to take the data for this category from.
269
+ # * lat_from: The data category to take the data for the latitude from.
270
+ # * lng_from: The data category to take the data for the longitude from.
265
271
  #
266
- # TODO Redo. Will have to write a wrapper that combines two categories that are indexed simultaneously.
272
+ # TODO Will have to write a wrapper that combines two categories that are
273
+ # indexed simultaneously, since lat/lng are correlated.
267
274
  #
268
- def define_map_location name, radius, options = {} # :nodoc:
269
- # The radius is given as if all the locations were on the equator.
275
+ def geo_categories lat_name, lng_name, radius, options = {} # :nodoc:
276
+
277
+ # Extract lat/lng specific options.
270
278
  #
271
- # TODO Need to recalculate since not many locations are on the equator ;) This is just a prototype.
279
+ lat_from = options.delete :lat_from
280
+ lng_from = options.delete :lng_from
281
+
282
+ # One can be a normal ranged_category.
283
+ #
284
+ ranged_category lat_name, radius*0.00898312, options.merge(from: lat_from)
285
+
286
+ # The other needs to adapt the radius depending on the one.
287
+ #
288
+ # Depending on the latitude, the radius of the longitude
289
+ # needs to enlarge, the closer we get to the pole.
290
+ #
291
+ # In our simplified case, the radius is given as if all the
292
+ # locations were on the 45 degree line.
272
293
  #
273
294
  # This calculates km -> longitude (degrees).
274
295
  #
275
- # A degree on the equator is equal to ~111,319.9 meters.
276
- # So a km on the equator is equal to 0.00898312 degrees.
296
+ # A degree on the 45 degree line is equal to ~222.6398 km.
297
+ # So a km on the 45 degree line is equal to 0.01796624 degrees.
277
298
  #
278
- define_ranged_category name, radius * 0.00898312, options
299
+ ranged_category lng_name, radius*0.01796624, options.merge(from: lng_from)
300
+
279
301
  end
280
- alias map_location define_map_location
302
+ alias define_geo_categories geo_categories
281
303
  end
282
304
 
283
305
  end
@@ -2,8 +2,6 @@ module Internals
2
2
 
3
3
  module Indexed # :nodoc:all
4
4
 
5
- # TODO Rewrite.
6
- #
7
5
  # A Bundle is a number of indexes
8
6
  # per [index, category] combination.
9
7
  #
@@ -105,8 +105,6 @@ module Internals
105
105
  # (Also none of the categories matched, but the ignore unassigned
106
106
  # tokens option is true)
107
107
  #
108
- # TODO Could use Combinations class here and remove the inject.
109
- #
110
108
  def possible_for token, preselected_categories = nil
111
109
  possible = (preselected_categories || possible_categories(token)).inject([]) do |combinations, category|
112
110
  combination = category.combination_for token
@@ -12,8 +12,6 @@ module Internals
12
12
  :analyze,
13
13
  :to => :categories
14
14
 
15
- # TODO Externalize?
16
- #
17
15
  def initialize name, options = {}
18
16
  @name = name
19
17
 
@@ -24,8 +22,6 @@ module Internals
24
22
  @categories = Categories.new ignore_unassigned_tokens: ignore_unassigned_tokens
25
23
  end
26
24
 
27
- # TODO Doc. Externalize?
28
- #
29
25
  def define_category category_name, options = {}
30
26
  options = default_category_options.merge options
31
27
 
@@ -4,15 +4,11 @@ module Internals
4
4
  #
5
5
  module Indexed
6
6
 
7
- # TODO Spec
8
- #
9
7
  module Wrappers
10
8
 
11
9
  # This index combines an exact and partial index.
12
10
  # It serves to order the results such that exact hits are found first.
13
11
  #
14
- # TODO Need to use the right subtokens. Bake in?
15
- #
16
12
  class ExactFirst < Indexed::Bundle::Base
17
13
 
18
14
  delegate :similar,
@@ -42,8 +38,6 @@ module Internals
42
38
  new index_or_category
43
39
  end
44
40
  end
45
- # TODO Do not extract categories!
46
- #
47
41
  def self.wrap_each_of categories
48
42
  categories.categories.collect! { |category| new(category) }
49
43
  end
@@ -6,8 +6,6 @@ module Indexers
6
6
  #
7
7
  # Note: It is called serial since it indexes each
8
8
  #
9
- # FIXME Giving the serial a category would be enough, since it already contains an index!
10
- #
11
9
  class Serial < Base
12
10
 
13
11
  attr_reader :category
@@ -24,22 +22,13 @@ module Indexers
24
22
  @tokenizer ||= category.tokenizer
25
23
  end
26
24
 
25
+ # Harvest the data from the source, tokenize,
26
+ # and write to an intermediate "prepared index" file.
27
+ #
27
28
  def process
28
29
  comma = ?,
29
30
  newline = ?\n
30
31
 
31
- # TODO Move open to config?
32
- #
33
- # @category.prepared_index do |file|
34
- # source.harvest(@index, @category) do |indexed_id, text|
35
- # tokenizer.tokenize(text).each do |token_text|
36
- # next unless token_text
37
- # file.buffer indexed_id << comma << token_text << newline
38
- # end
39
- # file.write_maybe
40
- # end
41
- # end
42
- #
43
32
  local_tokenizer = tokenizer
44
33
  category.prepared_index_file do |file|
45
34
  result = []
@@ -3,26 +3,26 @@
3
3
  module Internals
4
4
 
5
5
  module Indexing # :nodoc:all
6
-
6
+
7
7
  module Bundle
8
-
9
- # The memory version dumps its generated indexes to disk
10
- # (mostly JSON) to load them into memory on startup.
8
+
9
+ # The Redis version dumps its generated indexes to
10
+ # the Redis backend.
11
11
  #
12
12
  class Redis < Base
13
-
13
+
14
14
  attr_reader :backend
15
-
16
- def initialize name, configuration, *args
17
- super name, configuration, *args
18
-
19
- @backend = Internals::Index::Redis.new name, configuration # TODO Needed?
15
+
16
+ def initialize name, category, *args
17
+ super name, category, *args
18
+
19
+ @backend = Internals::Index::Redis.new name, category
20
20
  end
21
-
21
+
22
22
  end
23
-
23
+
24
24
  end
25
-
25
+
26
26
  end
27
-
27
+
28
28
  end
@@ -44,10 +44,12 @@ module Internals
44
44
  # [combination, combination] # of token 3
45
45
  # ]
46
46
  #
47
- def possible_combinations_in type
47
+ def possible_combinations_in index
48
48
  @tokens.inject([]) do |combinations, token|
49
- possible_combinations = token.possible_combinations_in type
49
+ possible_combinations = token.possible_combinations_in index
50
50
 
51
+ # TODO Could move the ignore_unassigned_tokens here!
52
+ #
51
53
  # Note: Optimization for ignoring tokens that allocate to nothing and
52
54
  # can be ignored.
53
55
  # For example in a special search, where "florian" is not
@@ -117,7 +117,7 @@ Case sensitive? #{@case_sensitive ? "Yes." : "-"}
117
117
 
118
118
  # Reject tokens after tokenizing based on the given criteria.
119
119
  #
120
- # Note: Currently only for indexing. TODO Redesign and write for both!
120
+ # Note: Currently only for indexing.
121
121
  #
122
122
  def reject_token_if &condition
123
123
  @reject_condition = condition
@@ -6,8 +6,6 @@ require 'spec_helper'
6
6
  #
7
7
  require File.expand_path '../../../../aux/picky/cli', __FILE__
8
8
 
9
- # TODO Finish this prototype Spec, redesign.
10
- #
11
9
  describe Picky::CLI do
12
10
 
13
11
  describe '.mapping' do
@@ -53,6 +53,15 @@ ERROR
53
53
  context 'unit' do
54
54
  let(:api) { described_class.new :some_index_name, source: some_source }
55
55
 
56
+ describe 'geo_categories' do
57
+ it 'delegates correctly' do
58
+ api.should_receive(:ranged_category).once.with :some_lat, 0.00898312, from: :some_lat_from
59
+ api.should_receive(:ranged_category).once.with :some_lng, 0.01796624, from: :some_lng_from
60
+
61
+ api.geo_categories :some_lat, :some_lng, 1, :lat_from => :some_lat_from, :lng_from => :some_lng_from
62
+ end
63
+ end
64
+
56
65
  describe 'define_source' do
57
66
  it 'delegates to the internal indexing' do
58
67
  indexing = stub :indexing
@@ -11,22 +11,22 @@ describe Internals::Indexed::Wrappers::ExactFirst do
11
11
  end
12
12
 
13
13
  describe "self.wrap" do
14
- context "type" do
15
- # FIXME
16
- #
17
- # it "wraps each category" do
18
- # type = Index::Type.new :type_name
19
- # type.define_category :some_category
20
- #
21
- # Index::Wrappers::ExactFirst.wrap type
22
- #
23
- # type.categories.should be_kind_of(Index::Wrappers::ExactFirst)
24
- # end
25
- it "returns the type" do
26
- type = Internals::Indexed::Index.new :type_name
27
- type.define_category :some_category
14
+ context "index" do
15
+ it "wraps each category" do
16
+ index = Internals::Indexed::Index.new :index_name
17
+ index.define_category :some_category
18
+
19
+ Internals::Indexed::Wrappers::ExactFirst.wrap index
20
+
21
+ index.categories.categories.each do |category|
22
+ category.should be_kind_of(Internals::Indexed::Wrappers::ExactFirst)
23
+ end
24
+ end
25
+ it "returns the index" do
26
+ index = Internals::Indexed::Index.new :index_name
27
+ index.define_category :some_category
28
28
 
29
- described_class.wrap(type).should == type
29
+ described_class.wrap(index).should == index
30
30
  end
31
31
  end
32
32
  context "category" do
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: picky
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 2.2.0
5
+ version: 2.2.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Florian Hanke
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-04-14 00:00:00 +10:00
13
+ date: 2011-04-15 00:00:00 +10:00
14
14
  default_executable: picky
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency