robsharp-extlib 0.9.15

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 (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