sundbp-extlib 0.9.14

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