sundbp-extlib 0.9.14

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