robsharp-extlib 0.9.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.autotest +21 -0
  2. data/.document +5 -0
  3. data/.gitignore +22 -0
  4. data/LICENSE +47 -0
  5. data/README.rdoc +17 -0
  6. data/Rakefile +28 -0
  7. data/VERSION +1 -0
  8. data/extlib.gemspec +147 -0
  9. data/lib/extlib.rb +50 -0
  10. data/lib/extlib/array.rb +38 -0
  11. data/lib/extlib/assertions.rb +8 -0
  12. data/lib/extlib/blank.rb +89 -0
  13. data/lib/extlib/boolean.rb +11 -0
  14. data/lib/extlib/byte_array.rb +6 -0
  15. data/lib/extlib/class.rb +179 -0
  16. data/lib/extlib/datetime.rb +29 -0
  17. data/lib/extlib/dictionary.rb +433 -0
  18. data/lib/extlib/hash.rb +450 -0
  19. data/lib/extlib/hook.rb +407 -0
  20. data/lib/extlib/inflection.rb +442 -0
  21. data/lib/extlib/lazy_array.rb +453 -0
  22. data/lib/extlib/lazy_module.rb +18 -0
  23. data/lib/extlib/logger.rb +198 -0
  24. data/lib/extlib/mash.rb +157 -0
  25. data/lib/extlib/module.rb +51 -0
  26. data/lib/extlib/nil.rb +5 -0
  27. data/lib/extlib/numeric.rb +5 -0
  28. data/lib/extlib/object.rb +178 -0
  29. data/lib/extlib/object_space.rb +13 -0
  30. data/lib/extlib/pathname.rb +20 -0
  31. data/lib/extlib/pooling.rb +235 -0
  32. data/lib/extlib/rubygems.rb +38 -0
  33. data/lib/extlib/simple_set.rb +66 -0
  34. data/lib/extlib/string.rb +176 -0
  35. data/lib/extlib/struct.rb +17 -0
  36. data/lib/extlib/symbol.rb +21 -0
  37. data/lib/extlib/time.rb +44 -0
  38. data/lib/extlib/try_dup.rb +44 -0
  39. data/lib/extlib/virtual_file.rb +10 -0
  40. data/spec/array_spec.rb +40 -0
  41. data/spec/blank_spec.rb +86 -0
  42. data/spec/byte_array_spec.rb +8 -0
  43. data/spec/class_spec.rb +158 -0
  44. data/spec/datetime_spec.rb +22 -0
  45. data/spec/hash_spec.rb +536 -0
  46. data/spec/hook_spec.rb +1235 -0
  47. data/spec/inflection/plural_spec.rb +565 -0
  48. data/spec/inflection/singular_spec.rb +498 -0
  49. data/spec/inflection_extras_spec.rb +111 -0
  50. data/spec/lazy_array_spec.rb +1961 -0
  51. data/spec/lazy_module_spec.rb +38 -0
  52. data/spec/mash_spec.rb +312 -0
  53. data/spec/module_spec.rb +71 -0
  54. data/spec/object_space_spec.rb +10 -0
  55. data/spec/object_spec.rb +114 -0
  56. data/spec/pooling_spec.rb +511 -0
  57. data/spec/rcov.opts +6 -0
  58. data/spec/simple_set_spec.rb +58 -0
  59. data/spec/spec.opts +4 -0
  60. data/spec/spec_helper.rb +7 -0
  61. data/spec/string_spec.rb +222 -0
  62. data/spec/struct_spec.rb +13 -0
  63. data/spec/symbol_spec.rb +9 -0
  64. data/spec/time_spec.rb +31 -0
  65. data/spec/try_call_spec.rb +74 -0
  66. data/spec/try_dup_spec.rb +46 -0
  67. data/spec/virtual_file_spec.rb +22 -0
  68. data/tasks/ci.rake +1 -0
  69. data/tasks/metrics.rake +36 -0
  70. data/tasks/spec.rake +25 -0
  71. data/tasks/yard.rake +9 -0
  72. data/tasks/yardstick.rake +19 -0
  73. metadata +198 -0
@@ -0,0 +1,442 @@
1
+ require 'extlib/string'
2
+
3
+ module Extlib
4
+
5
+ # = English Nouns Number Inflection.
6
+ #
7
+ # This module provides english singular <-> plural noun inflections.
8
+ module Inflection
9
+
10
+ class << self
11
+ # Take an underscored name and make it into a camelized name
12
+ #
13
+ # @example
14
+ # "egg_and_hams".classify #=> "EggAndHam"
15
+ # "enlarged_testes".classify #=> "EnlargedTestis"
16
+ # "post".classify #=> "Post"
17
+ #
18
+ def classify(name)
19
+ words = name.to_s.sub(/.*\./, '').split('_')
20
+ words[-1] = singularize(words[-1])
21
+ words.collect { |word| word.capitalize }.join
22
+ end
23
+
24
+ # By default, camelize converts strings to UpperCamelCase.
25
+ #
26
+ # camelize will also convert '/' to '::' which is useful for converting paths to namespaces
27
+ #
28
+ # @example
29
+ # "active_record".camelize #=> "ActiveRecord"
30
+ # "active_record/errors".camelize #=> "ActiveRecord::Errors"
31
+ #
32
+ def camelize(lower_case_and_underscored_word, *args)
33
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
34
+ end
35
+
36
+
37
+ # The reverse of +camelize+. Makes an underscored form from the expression in the string.
38
+ #
39
+ # Changes '::' to '/' to convert namespaces to paths.
40
+ #
41
+ # @example
42
+ # "ActiveRecord".underscore #=> "active_record"
43
+ # "ActiveRecord::Errors".underscore #=> active_record/errors
44
+ #
45
+ def underscore(camel_cased_word)
46
+ camel_cased_word.to_const_path
47
+ end
48
+
49
+ # Capitalizes the first word and turns underscores into spaces and strips _id.
50
+ # Like titleize, this is meant for creating pretty output.
51
+ #
52
+ # @example
53
+ # "employee_salary" #=> "Employee salary"
54
+ # "author_id" #=> "Author"
55
+ def humanize(lower_case_and_underscored_word)
56
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, '').tr('_', ' ').capitalize
57
+ end
58
+
59
+ # Removes the module part from the expression in the string
60
+ #
61
+ # @example
62
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections"
63
+ # "Inflections".demodulize #=> "Inflections"
64
+ def demodulize(class_name_in_module)
65
+ class_name_in_module.to_s.gsub(/^.*::/, '')
66
+ end
67
+
68
+ # Create the name of a table like Rails does for models to table names. This method
69
+ # uses the pluralize method on the last word in the string.
70
+ #
71
+ # @example
72
+ # "RawScaledScorer".tableize #=> "raw_scaled_scorers"
73
+ # "EnlargedTestis".tableize #=> "enlarged_testes"
74
+ # "egg_and_ham".tableize #=> "egg_and_hams"
75
+ # "fancyCategory".tableize #=> "fancy_categories"
76
+ def tableize(class_name)
77
+ words = class_name.to_const_path.tr('/', '_').split('_')
78
+ words[-1] = pluralize(words[-1])
79
+ words.join('_')
80
+ end
81
+
82
+ # Creates a foreign key name from a class name.
83
+ #
84
+ # @example
85
+ # "Message".foreign_key #=> "message_id"
86
+ # "Admin::Post".foreign_key #=> "post_id"
87
+ def foreign_key(class_name, key = "id")
88
+ underscore(demodulize(class_name.to_s)) << "_" << key.to_s
89
+ end
90
+
91
+ # Constantize tries to find a declared constant with the name specified
92
+ # in the string. It raises a NameError when the name is not in CamelCase
93
+ # or is not initialized.
94
+ #
95
+ # @example
96
+ # "Module".constantize #=> Module
97
+ # "Class".constantize #=> Class
98
+ def constantize(camel_cased_word)
99
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
100
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
101
+ end
102
+
103
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
104
+ end
105
+ end
106
+
107
+ @singular_of = {}
108
+ @plural_of = {}
109
+
110
+ @singular_rules = []
111
+ @plural_rules = []
112
+
113
+ class << self
114
+ # Defines a general inflection exception case.
115
+ #
116
+ # ==== Parameters
117
+ # singular<String>::
118
+ # singular form of the word
119
+ # plural<String>::
120
+ # plural form of the word
121
+ #
122
+ # ==== Examples
123
+ #
124
+ # Here we define erratum/errata exception case:
125
+ #
126
+ # English::Inflect.word "erratum", "errata"
127
+ #
128
+ # In case singular and plural forms are the same omit
129
+ # second argument on call:
130
+ #
131
+ # English::Inflect.word 'information'
132
+ def word(singular, plural=nil)
133
+ plural = singular unless plural
134
+ singular_word(singular, plural)
135
+ plural_word(singular, plural)
136
+ end
137
+
138
+ def clear(type = :all)
139
+ if type == :singular || type == :all
140
+ @singular_of = {}
141
+ @singular_rules = []
142
+ @singularization_rules, @singularization_regex = nil, nil
143
+ end
144
+ if type == :plural || type == :all
145
+ @singular_of = {}
146
+ @singular_rules = []
147
+ @singularization_rules, @singularization_regex = nil, nil
148
+ end
149
+ end
150
+
151
+
152
+ # Define a singularization exception.
153
+ #
154
+ # ==== Parameters
155
+ # singular<String>::
156
+ # singular form of the word
157
+ # plural<String>::
158
+ # plural form of the word
159
+ def singular_word(singular, plural)
160
+ @singular_of[plural] = singular
161
+ @singular_of[plural.capitalize] = singular.capitalize
162
+ end
163
+
164
+ # Define a pluralization exception.
165
+ #
166
+ # ==== Parameters
167
+ # singular<String>::
168
+ # singular form of the word
169
+ # plural<String>::
170
+ # plural form of the word
171
+ def plural_word(singular, plural)
172
+ @plural_of[singular] = plural
173
+ @plural_of[singular.capitalize] = plural.capitalize
174
+ end
175
+
176
+ # Define a general rule.
177
+ #
178
+ # ==== Parameters
179
+ # singular<String>::
180
+ # ending of the word in singular form
181
+ # plural<String>::
182
+ # ending of the word in plural form
183
+ # whole_word<Boolean>::
184
+ # for capitalization, since words can be
185
+ # capitalized (Man => Men) #
186
+ # ==== Examples
187
+ # Once the following rule is defined:
188
+ # English::Inflect.rule 'y', 'ies'
189
+ #
190
+ # You can see the following results:
191
+ # irb> "fly".plural
192
+ # => flies
193
+ # irb> "cry".plural
194
+ # => cries
195
+ # Define a general rule.
196
+
197
+ def rule(singular, plural, whole_word = false)
198
+ singular_rule(singular, plural)
199
+ plural_rule(singular, plural)
200
+ word(singular, plural) if whole_word
201
+ end
202
+
203
+ # Define a singularization rule.
204
+ #
205
+ # ==== Parameters
206
+ # singular<String>::
207
+ # ending of the word in singular form
208
+ # plural<String>::
209
+ # ending of the word in plural form
210
+ #
211
+ # ==== Examples
212
+ # Once the following rule is defined:
213
+ # English::Inflect.singular_rule 'o', 'oes'
214
+ #
215
+ # You can see the following results:
216
+ # irb> "heroes".singular
217
+ # => hero
218
+ def singular_rule(singular, plural)
219
+ @singular_rules << [singular, plural]
220
+ end
221
+
222
+ # Define a plurualization rule.
223
+ #
224
+ # ==== Parameters
225
+ # singular<String>::
226
+ # ending of the word in singular form
227
+ # plural<String>::
228
+ # ending of the word in plural form
229
+ #
230
+ # ==== Examples
231
+ # Once the following rule is defined:
232
+ # English::Inflect.singular_rule 'fe', 'ves'
233
+ #
234
+ # You can see the following results:
235
+ # irb> "wife".plural
236
+ # => wives
237
+ def plural_rule(singular, plural)
238
+ @plural_rules << [singular, plural]
239
+ end
240
+
241
+ # Read prepared singularization rules.
242
+ def singularization_rules
243
+ if defined?(@singularization_regex) && @singularization_regex
244
+ return [@singularization_regex, @singularization_hash]
245
+ end
246
+ # No sorting needed: Regexen match on longest string
247
+ @singularization_regex = Regexp.new("(" + @singular_rules.map {|s,p| p}.join("|") + ")$", "i")
248
+ @singularization_hash = Hash[*@singular_rules.flatten].invert
249
+ [@singularization_regex, @singularization_hash]
250
+ end
251
+
252
+ # Read prepared pluralization rules.
253
+ def pluralization_rules
254
+ if defined?(@pluralization_regex) && @pluralization_regex
255
+ return [@pluralization_regex, @pluralization_hash]
256
+ end
257
+ @pluralization_regex = Regexp.new("(" + @plural_rules.map {|s,p| s}.join("|") + ")$", "i")
258
+ @pluralization_hash = Hash[*@plural_rules.flatten]
259
+ [@pluralization_regex, @pluralization_hash]
260
+ end
261
+
262
+ attr_reader :singular_of, :plural_of
263
+
264
+ # Convert an English word from plural to singular.
265
+ #
266
+ # "boys".singular #=> boy
267
+ # "tomatoes".singular #=> tomato
268
+ #
269
+ # ==== Parameters
270
+ # word<String>:: word to singularize
271
+ #
272
+ # ==== Returns
273
+ # <String>:: singularized form of word
274
+ #
275
+ # ==== Notes
276
+ # Aliased as singularize (a Railism)
277
+ def singular(word)
278
+ if result = singular_of[word]
279
+ return result.dup
280
+ end
281
+ result = word.dup
282
+ regex, hash = singularization_rules
283
+ result.sub!(regex) {|m| hash[m]}
284
+ singular_of[word] = result
285
+ return result
286
+ end
287
+
288
+ # Alias for #singular (a Railism).
289
+ #
290
+ alias_method(:singularize, :singular)
291
+
292
+ # Convert an English word from singular to plural.
293
+ #
294
+ # "boy".plural #=> boys
295
+ # "tomato".plural #=> tomatoes
296
+ #
297
+ # ==== Parameters
298
+ # word<String>:: word to pluralize
299
+ #
300
+ # ==== Returns
301
+ # <String>:: pluralized form of word
302
+ #
303
+ # ==== Notes
304
+ # Aliased as pluralize (a Railism)
305
+ def plural(word)
306
+ # special exceptions
307
+ return "" if word == ""
308
+ if result = plural_of[word]
309
+ return result.dup
310
+ end
311
+ result = word.dup
312
+ regex, hash = pluralization_rules
313
+ result.sub!(regex) {|m| hash[m]}
314
+ plural_of[word] = result
315
+ return result
316
+ end
317
+
318
+ # Alias for #plural (a Railism).
319
+ alias_method(:pluralize, :plural)
320
+ end
321
+
322
+ # One argument means singular and plural are the same.
323
+
324
+ word 'equipment'
325
+ word 'information'
326
+ word 'money'
327
+ word 'species'
328
+ word 'series'
329
+ word 'fish'
330
+ word 'sheep'
331
+ word 'moose'
332
+ word 'hovercraft'
333
+ word 'grass'
334
+ word 'rain'
335
+ word 'milk'
336
+ word 'rice'
337
+ word 'plurals'
338
+ word 'postgres'
339
+ word 'status'
340
+
341
+ # Two arguments defines a singular and plural exception.
342
+ word 'status' , 'status'
343
+ word 'Swiss' , 'Swiss'
344
+ word 'life' , 'lives'
345
+ word 'wife' , 'wives'
346
+ word 'goose' , 'geese'
347
+ word 'criterion' , 'criteria'
348
+ word 'alias' , 'aliases'
349
+ word 'status' , 'statuses'
350
+ word 'axis' , 'axes'
351
+ word 'crisis' , 'crises'
352
+ word 'testis' , 'testes'
353
+ word 'potato' , 'potatoes'
354
+ word 'tomato' , 'tomatoes'
355
+ word 'buffalo' , 'buffaloes'
356
+ word 'torpedo' , 'torpedoes'
357
+ word 'quiz' , 'quizzes'
358
+ word 'matrix' , 'matrices'
359
+ word 'vertex' , 'vertices'
360
+ word 'index' , 'indices'
361
+ word 'ox' , 'oxen'
362
+ word 'mouse' , 'mice'
363
+ word 'louse' , 'lice'
364
+ word 'thesis' , 'theses'
365
+ word 'thief' , 'thieves'
366
+ word 'analysis' , 'analyses'
367
+ word 'erratum' , 'errata'
368
+ word 'phenomenon', 'phenomena'
369
+ word 'octopus' , 'octopi'
370
+ word 'thesaurus' , 'thesauri'
371
+ word 'movie' , 'movies'
372
+ word 'cactus' , 'cacti'
373
+ word 'plus' , 'plusses'
374
+ word 'cross' , 'crosses'
375
+ word 'medium' , 'media'
376
+ word 'datum' , 'data'
377
+ word 'basis' , 'bases'
378
+ word 'diagnosis' , 'diagnoses'
379
+
380
+ # One-way singularization exception (convert plural to singular).
381
+
382
+ # General rules.
383
+ rule 'person' , 'people', true
384
+ rule 'shoe' , 'shoes', true
385
+ rule 'hive' , 'hives', true
386
+ rule 'man' , 'men', true
387
+ rule 'child' , 'children', true
388
+ rule 'news' , 'news', true
389
+ rule 'rf' , 'rves'
390
+ rule 'af' , 'aves'
391
+ rule 'ero' , 'eroes'
392
+ rule 'man' , 'men'
393
+ rule 'ch' , 'ches'
394
+ rule 'sh' , 'shes'
395
+ rule 'ss' , 'sses'
396
+ rule 'ta' , 'tum'
397
+ rule 'ia' , 'ium'
398
+ rule 'ra' , 'rum'
399
+ rule 'ay' , 'ays'
400
+ rule 'ey' , 'eys'
401
+ rule 'oy' , 'oys'
402
+ rule 'uy' , 'uys'
403
+ rule 'y' , 'ies'
404
+ rule 'x' , 'xes'
405
+ rule 'lf' , 'lves'
406
+ rule 'ffe' , 'ffes'
407
+ rule 'afe' , 'aves'
408
+ rule 'ouse' , 'ouses'
409
+ # more cases of words ending in -oses not being singularized properly
410
+ # than cases of words ending in -osis
411
+ # rule 'osis' , 'oses'
412
+ rule 'ox' , 'oxes'
413
+ rule 'us' , 'uses'
414
+ rule '' , 's'
415
+
416
+ # One-way singular rules.
417
+
418
+ singular_rule 'of' , 'ofs' # proof
419
+ singular_rule 'o' , 'oes' # hero, heroes
420
+ singular_rule 'f' , 'ves'
421
+
422
+ # One-way plural rules.
423
+
424
+ #plural_rule 'fe' , 'ves' # safe, wife
425
+ plural_rule 's' , 'ses'
426
+ plural_rule 'ive' , 'ives' # don't want to snag wife
427
+ plural_rule 'fe' , 'ves' # don't want to snag perspectives
428
+
429
+
430
+ end
431
+ end
432
+
433
+ class String
434
+ def singular
435
+ Extlib::Inflection.singular(self)
436
+ end
437
+ alias_method(:singularize, :singular)
438
+ def plural
439
+ Extlib::Inflection.plural(self)
440
+ end
441
+ alias_method(:pluralize, :plural)
442
+ end
@@ -0,0 +1,453 @@
1
+ require 'extlib/try_dup'
2
+
3
+ class LazyArray # borrowed partially from StrokeDB
4
+ include Enumerable
5
+
6
+ attr_reader :head, :tail
7
+
8
+ def first(*args)
9
+ if lazy_possible?(@head, *args)
10
+ @head.first(*args)
11
+ else
12
+ lazy_load
13
+ @array.first(*args)
14
+ end
15
+ end
16
+
17
+ def last(*args)
18
+ if lazy_possible?(@tail, *args)
19
+ @tail.last(*args)
20
+ else
21
+ lazy_load
22
+ @array.last(*args)
23
+ end
24
+ end
25
+
26
+ def at(index)
27
+ if index >= 0 && lazy_possible?(@head, index + 1)
28
+ @head.at(index)
29
+ elsif index < 0 && lazy_possible?(@tail, index.abs)
30
+ @tail.at(index)
31
+ else
32
+ lazy_load
33
+ @array.at(index)
34
+ end
35
+ end
36
+
37
+ def fetch(*args, &block)
38
+ index = args.first
39
+
40
+ if index >= 0 && lazy_possible?(@head, index + 1)
41
+ @head.fetch(*args, &block)
42
+ elsif index < 0 && lazy_possible?(@tail, index.abs)
43
+ @tail.fetch(*args, &block)
44
+ else
45
+ lazy_load
46
+ @array.fetch(*args, &block)
47
+ end
48
+ end
49
+
50
+ def values_at(*args)
51
+ accumulator = []
52
+
53
+ lazy_possible = args.all? do |arg|
54
+ index, length = extract_slice_arguments(arg)
55
+
56
+ if index >= 0 && lazy_possible?(@head, index + length)
57
+ accumulator.concat(head.values_at(*arg))
58
+ elsif index < 0 && lazy_possible?(@tail, index.abs)
59
+ accumulator.concat(tail.values_at(*arg))
60
+ end
61
+ end
62
+
63
+ if lazy_possible
64
+ accumulator
65
+ else
66
+ lazy_load
67
+ @array.values_at(*args)
68
+ end
69
+ end
70
+
71
+ def index(entry)
72
+ (lazy_possible?(@head) && @head.index(entry)) || begin
73
+ lazy_load
74
+ @array.index(entry)
75
+ end
76
+ end
77
+
78
+ def include?(entry)
79
+ (lazy_possible?(@tail) && @tail.include?(entry)) ||
80
+ (lazy_possible?(@head) && @head.include?(entry)) || begin
81
+ lazy_load
82
+ @array.include?(entry)
83
+ end
84
+ end
85
+
86
+ def empty?
87
+ (@tail.nil? || @tail.empty?) &&
88
+ (@head.nil? || @head.empty?) && begin
89
+ lazy_load
90
+ @array.empty?
91
+ end
92
+ end
93
+
94
+ def any?(&block)
95
+ (lazy_possible?(@tail) && @tail.any?(&block)) ||
96
+ (lazy_possible?(@head) && @head.any?(&block)) || begin
97
+ lazy_load
98
+ @array.any?(&block)
99
+ end
100
+ end
101
+
102
+ def [](*args)
103
+ index, length = extract_slice_arguments(*args)
104
+
105
+ if length == 1 && args.size == 1 && args.first.kind_of?(Integer)
106
+ return at(index)
107
+ end
108
+
109
+ if index >= 0 && lazy_possible?(@head, index + length)
110
+ @head[*args]
111
+ elsif index < 0 && lazy_possible?(@tail, index.abs - 1 + length)
112
+ @tail[*args]
113
+ else
114
+ lazy_load
115
+ @array[*args]
116
+ end
117
+ end
118
+
119
+ alias slice []
120
+
121
+ def slice!(*args)
122
+ index, length = extract_slice_arguments(*args)
123
+
124
+ if index >= 0 && lazy_possible?(@head, index + length)
125
+ @head.slice!(*args)
126
+ elsif index < 0 && lazy_possible?(@tail, index.abs - 1 + length)
127
+ @tail.slice!(*args)
128
+ else
129
+ lazy_load
130
+ @array.slice!(*args)
131
+ end
132
+ end
133
+
134
+ def []=(*args)
135
+ index, length = extract_slice_arguments(*args[0..-2])
136
+
137
+ if index >= 0 && lazy_possible?(@head, index + length)
138
+ @head.[]=(*args)
139
+ elsif index < 0 && lazy_possible?(@tail, index.abs - 1 + length)
140
+ @tail.[]=(*args)
141
+ else
142
+ lazy_load
143
+ @array.[]=(*args)
144
+ end
145
+ end
146
+
147
+ alias splice []=
148
+
149
+ def reverse
150
+ dup.reverse!
151
+ end
152
+
153
+ def reverse!
154
+ # reverse without kicking if possible
155
+ if loaded?
156
+ @array = @array.reverse
157
+ else
158
+ @head, @tail = @tail.reverse, @head.reverse
159
+
160
+ proc = @load_with_proc
161
+
162
+ @load_with_proc = lambda do |v|
163
+ proc.call(v)
164
+ v.instance_variable_get(:@array).reverse!
165
+ end
166
+ end
167
+
168
+ self
169
+ end
170
+
171
+ def <<(entry)
172
+ if loaded?
173
+ lazy_load
174
+ @array << entry
175
+ else
176
+ @tail << entry
177
+ end
178
+ self
179
+ end
180
+
181
+ def concat(other)
182
+ if loaded?
183
+ lazy_load
184
+ @array.concat(other)
185
+ else
186
+ @tail.concat(other)
187
+ end
188
+ self
189
+ end
190
+
191
+ def push(*entries)
192
+ if loaded?
193
+ lazy_load
194
+ @array.push(*entries)
195
+ else
196
+ @tail.push(*entries)
197
+ end
198
+ self
199
+ end
200
+
201
+ def unshift(*entries)
202
+ if loaded?
203
+ lazy_load
204
+ @array.unshift(*entries)
205
+ else
206
+ @head.unshift(*entries)
207
+ end
208
+ self
209
+ end
210
+
211
+ def insert(index, *entries)
212
+ if index >= 0 && lazy_possible?(@head, index)
213
+ @head.insert(index, *entries)
214
+ elsif index < 0 && lazy_possible?(@tail, index.abs - 1)
215
+ @tail.insert(index, *entries)
216
+ else
217
+ lazy_load
218
+ @array.insert(index, *entries)
219
+ end
220
+ self
221
+ end
222
+
223
+ def pop(*args)
224
+ if lazy_possible?(@tail, *args)
225
+ @tail.pop(*args)
226
+ else
227
+ lazy_load
228
+ @array.pop(*args)
229
+ end
230
+ end
231
+
232
+ def shift(*args)
233
+ if lazy_possible?(@head, *args)
234
+ @head.shift(*args)
235
+ else
236
+ lazy_load
237
+ @array.shift(*args)
238
+ end
239
+ end
240
+
241
+ def delete_at(index)
242
+ if index >= 0 && lazy_possible?(@head, index + 1)
243
+ @head.delete_at(index)
244
+ elsif index < 0 && lazy_possible?(@tail, index.abs)
245
+ @tail.delete_at(index)
246
+ else
247
+ lazy_load
248
+ @array.delete_at(index)
249
+ end
250
+ end
251
+
252
+ def delete_if(&block)
253
+ if loaded?
254
+ lazy_load
255
+ @array.delete_if(&block)
256
+ else
257
+ @reapers << block
258
+ @head.delete_if(&block)
259
+ @tail.delete_if(&block)
260
+ end
261
+ self
262
+ end
263
+
264
+ def replace(other)
265
+ mark_loaded
266
+ @array.replace(other)
267
+ self
268
+ end
269
+
270
+ def clear
271
+ mark_loaded
272
+ @array.clear
273
+ self
274
+ end
275
+
276
+ def to_a
277
+ lazy_load
278
+ @array.to_a
279
+ end
280
+
281
+ alias to_ary to_a
282
+
283
+ def load_with(&block)
284
+ @load_with_proc = block
285
+ self
286
+ end
287
+
288
+ def loaded?
289
+ @loaded == true
290
+ end
291
+
292
+ def kind_of?(klass)
293
+ super || @array.kind_of?(klass)
294
+ end
295
+
296
+ alias is_a? kind_of?
297
+
298
+ def respond_to?(method, include_private = false)
299
+ super || @array.respond_to?(method)
300
+ end
301
+
302
+ def freeze
303
+ if loaded?
304
+ @array.freeze
305
+ else
306
+ @head.freeze
307
+ @tail.freeze
308
+ end
309
+ @frozen = true
310
+ self
311
+ end
312
+
313
+ def frozen?
314
+ @frozen == true
315
+ end
316
+
317
+ def ==(other)
318
+ if equal?(other)
319
+ return true
320
+ end
321
+
322
+ unless other.respond_to?(:to_ary)
323
+ return false
324
+ end
325
+
326
+ # if necessary, convert to something that can be compared
327
+ other = other.to_ary unless other.respond_to?(:[])
328
+
329
+ cmp?(other, :==)
330
+ end
331
+
332
+ def eql?(other)
333
+ if equal?(other)
334
+ return true
335
+ end
336
+
337
+ unless other.class.equal?(self.class)
338
+ return false
339
+ end
340
+
341
+ cmp?(other, :eql?)
342
+ end
343
+
344
+ def lazy_possible?(list, need_length = 1)
345
+ !loaded? && need_length <= list.size
346
+ end
347
+
348
+ private
349
+
350
+ def initialize
351
+ @frozen = false
352
+ @loaded = false
353
+ @load_with_proc = lambda { |v| v }
354
+ @head = []
355
+ @tail = []
356
+ @array = []
357
+ @reapers = []
358
+ end
359
+
360
+ def initialize_copy(original)
361
+ @head = @head.try_dup
362
+ @tail = @tail.try_dup
363
+ @array = @array.try_dup
364
+ end
365
+
366
+ def lazy_load
367
+ return if loaded?
368
+ mark_loaded
369
+ @load_with_proc[self]
370
+ @array.unshift(*@head)
371
+ @array.concat(@tail)
372
+ @head = @tail = nil
373
+ @reapers.each { |r| @array.delete_if(&r) } if @reapers
374
+ @array.freeze if frozen?
375
+ end
376
+
377
+ def mark_loaded
378
+ @loaded = true
379
+ end
380
+
381
+ ##
382
+ # Extract arguments for #slice an #slice! and return index and length
383
+ #
384
+ # @param [Integer, Array(Integer), Range] *args the index,
385
+ # index and length, or range indicating first and last position
386
+ #
387
+ # @return [Integer] the index
388
+ # @return [Integer,NilClass] the length, if any
389
+ #
390
+ # @api private
391
+ def extract_slice_arguments(*args)
392
+ first_arg, second_arg = args
393
+
394
+ if args.size == 2 && first_arg.kind_of?(Integer) && second_arg.kind_of?(Integer)
395
+ return first_arg, second_arg
396
+ elsif args.size == 1
397
+ if first_arg.kind_of?(Integer)
398
+ return first_arg, 1
399
+ elsif first_arg.kind_of?(Range)
400
+ index = first_arg.first
401
+ length = first_arg.last - index
402
+ length += 1 unless first_arg.exclude_end?
403
+ return index, length
404
+ end
405
+ end
406
+
407
+ raise ArgumentError, "arguments may be 1 or 2 Integers, or 1 Range object, was: #{args.inspect}", caller(1)
408
+ end
409
+
410
+ def each
411
+ lazy_load
412
+ if block_given?
413
+ @array.each { |entry| yield entry }
414
+ self
415
+ else
416
+ @array.each
417
+ end
418
+ end
419
+
420
+ # delegate any not-explicitly-handled methods to @array, if possible.
421
+ # this is handy for handling methods mixed-into Array like group_by
422
+ def method_missing(method, *args, &block)
423
+ if @array.respond_to?(method)
424
+ lazy_load
425
+ results = @array.send(method, *args, &block)
426
+ results.equal?(@array) ? self : results
427
+ else
428
+ super
429
+ end
430
+ end
431
+
432
+ def cmp?(other, operator)
433
+ unless loaded?
434
+ # compare the head against the beginning of other. start at index
435
+ # 0 and incrementally compare each entry. if other is a LazyArray
436
+ # this has a lesser likelyhood of triggering a lazy load
437
+ 0.upto(@head.size - 1) do |i|
438
+ return false unless @head[i].__send__(operator, other[i])
439
+ end
440
+
441
+ # compare the tail against the end of other. start at index
442
+ # -1 and decrementally compare each entry. if other is a LazyArray
443
+ # this has a lesser likelyhood of triggering a lazy load
444
+ -1.downto(@tail.size * -1) do |i|
445
+ return false unless @tail[i].__send__(operator, other[i])
446
+ end
447
+
448
+ lazy_load
449
+ end
450
+
451
+ @array.send(operator, other.to_ary)
452
+ end
453
+ end