libis-workflow-mongoid 2.0.2 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a259d21bb114e3b4397ecb6c1152769d9960965
4
- data.tar.gz: c75a90afe74196dd1ab779cd37fc7012f2b04c0b
3
+ metadata.gz: ae15d66c11b105c5d81e707bad684d3b7f9805ec
4
+ data.tar.gz: 1116380cbcc99ae1bf7ba0aac642f12c633feac9
5
5
  SHA512:
6
- metadata.gz: c3318624c4f63e725150382369c9bc29fedb501996cb37113cb7d57729090d024f81bf54bf5cdcd086b47e43d0c42e350b6d66342148024b77eeaca298debdae
7
- data.tar.gz: 8f2b4c72528be421539f498166943cdcd5f228d7f6799b8889f4bbd9e58265bfe591c8f5ed16f68f55d98b19671ebabdac72f9e62646a6dc2b0a4bd0f9732d4d
6
+ metadata.gz: 082fee48cc81d1a54b0e451b93ab4704aa86aaaf8d479946bfbeeff886eabee7628b43730266c027f08b61fcfaf6eb7ec11c2bf27c60f5dfffc33f0fb79f50da
7
+ data.tar.gz: 4563f3ec708ecc6e72d05e1b7485422dae39b06080a7afa5ddba4be5df6481ea9aeac5387dc4e234ca54549fab37da23e71709008534244f4c7ae0096848abfe
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec name: 'libis-workflow-mongoid', development_group: :test
4
-
@@ -3,6 +3,7 @@ require 'mongoid'
3
3
  require 'mongoid/document'
4
4
  require 'yaml'
5
5
  require 'libis/tools/extend/hash'
6
+ require 'map_with_indifferent_access'
6
7
 
7
8
  # require 'mongoid_indifferent_access'
8
9
  require_relative 'sequence'
@@ -15,6 +16,7 @@ module Libis
15
16
  module Base
16
17
 
17
18
  def self.included(klass)
19
+ klass.extend(ClassMethods)
18
20
  klass.class_eval do
19
21
  include ::Mongoid::Document
20
22
  include ::Mongoid::Timestamps::Created::Short
@@ -26,14 +28,50 @@ module Libis
26
28
  end
27
29
  end
28
30
 
31
+ module ClassMethods
32
+ def from_hash(hash)
33
+ self.create_from_hash(hash.cleanup, [:name])
34
+ end
35
+
36
+ def create_from_hash(hash, id_tags, &block)
37
+ hash = hash.key_strings_to_symbols
38
+ id_tags = id_tags.map(&:to_sym)
39
+ return nil unless id_tags.empty? || id_tags.any? { |k| hash.include?(k) }
40
+ tags = id_tags.inject({}) do |h, k|
41
+ v = hash.delete(k)
42
+ h[k] = v if v
43
+ h
44
+ end
45
+ item = tags.empty? ? self.new : self.find_or_initialize_by(tags)
46
+ block.call(item, hash) if block unless hash.empty?
47
+ item.assign_attributes(hash)
48
+ unless self.embedded?
49
+ item.save!
50
+ end
51
+ item
52
+ end
53
+
54
+ def indifferent_hash(field_name, method_name)
55
+ define_method method_name do
56
+ MapWithIndifferentAccess::Map.new(self.read_attribute(field_name))
57
+ end
58
+
59
+ define_method "#{method_name}=" do |value|
60
+ self.write_attribute(field_name, value)
61
+ self.send(method_name)
62
+ end
63
+ end
64
+
65
+ end
66
+
29
67
  def dup
30
68
  new_obj = self.class.new
31
69
  new_obj.copy_attributes(self)
32
70
  end
33
71
 
34
- def info
35
- result = self.attributes.reject { |k,v| v.blank? || volatile_attributes.include?(k) }
36
- result = result.to_yaml.gsub(/!ruby\/hash:BSON::Document/,'')
72
+ def to_hash
73
+ result = self.attributes.reject { |k, v| v.blank? || volatile_attributes.include?(k) }
74
+ result = result.to_yaml.gsub(/!ruby\/hash:BSON::Document/, '')
37
75
  # noinspection RubyResolve
38
76
  result = YAML.load(result)
39
77
  result.key_strings_to_symbols!(recursive: true)
@@ -48,6 +86,7 @@ module Libis
48
86
  def volatile_attributes
49
87
  %w'_id c_at'
50
88
  end
89
+
51
90
  private
52
91
 
53
92
  def copy_attributes(other)
@@ -1,5 +1,4 @@
1
- # encoding: utf-8
2
-
1
+ require 'map_with_indifferent_access'
3
2
  require 'libis/workflow/base/job'
4
3
  require 'libis/workflow/mongoid/base'
5
4
 
@@ -16,7 +15,7 @@ module Libis
16
15
 
17
16
  field :name, type: String
18
17
  field :description, type: String
19
- field :input, type: Hash, default: -> { Hash.new }
18
+ field :_input, type: Hash, default: -> { Hash.new }
20
19
  field :run_object, type: String
21
20
  field :log_to_file, type: Boolean, default: false
22
21
  field :log_each_run, type: Boolean, default: false
@@ -30,6 +29,29 @@ module Libis
30
29
 
31
30
  belongs_to :workflow, polymorphic: true
32
31
 
32
+ def self.from_hash(hash)
33
+ self.create_from_hash(hash, [:name]) do |item, cfg|
34
+ item.workflow = Libis::Workflow::Mongoid.from_hash(name: cfg.delete(:workflow))
35
+ end
36
+ end
37
+
38
+ # def input
39
+ # MapWithIndifferentAccess::Map.new(self.read_attribute(:_input))
40
+ # end
41
+ #
42
+ # def input=(hash)
43
+ # self.write_attribute(:_input, hash)
44
+ # self.input
45
+ # end
46
+
47
+ indifferent_hash :_input, :input
48
+
49
+ def to_hash
50
+ result = super
51
+ result[:input] = result.delete(:_input)
52
+ result
53
+ end
54
+
33
55
  def logger
34
56
  return ::Libis::Workflow::Mongoid::Config.logger unless self.log_to_file
35
57
  logger = ::Logging::Repository[self.name]
@@ -19,10 +19,14 @@ module Libis
19
19
  field :start_date, type: Time, default: -> { Time.now }
20
20
  field :log_to_file, type: Boolean, default: false
21
21
  field :log_level, type: String, default: 'DEBUG'
22
+ field :log_filename, type: String
22
23
 
23
24
  set_callback(:destroy, :before) do |document|
24
25
  wd = document.work_dir
25
26
  FileUtils.rmtree wd if wd && !wd.blank? && Dir.exist?(wd)
27
+ # noinspection RubyResolve
28
+ log_file = document.log_filename
29
+ FileUtils.rm(log_file) if log_file && !log_file.blank? && File.exist?(log_file)
26
30
  end
27
31
 
28
32
  index start_date: 1
@@ -30,23 +34,24 @@ module Libis
30
34
  belongs_to :job, polymorphic: true
31
35
  embeds_one :log_config, as: :log_configurator
32
36
 
33
- def run
37
+ def run(action = :run)
34
38
  self.tasks = []
35
39
  self.items = []
36
40
  # noinspection RubySuperCallWithoutSuperclassInspection
37
- super
41
+ super action
38
42
  end
39
43
 
40
44
  def logger
41
45
  unless self.log_to_file
42
46
  return self.job.logger
43
47
  end
44
- logger = ::Logging::Repository[self.name]
48
+ logger = ::Logging::Repository.instance[self.name]
45
49
  return logger if logger
46
50
  unless ::Logging::Appenders[self.name]
51
+ self.log_filename ||= File.join(::Libis::Workflow::Mongoid::Config[:log_dir], "#{self.name}.log")
47
52
  ::Logging::Appenders::File.new(
48
53
  self.name,
49
- filename: File.join(::Libis::Workflow::Mongoid::Config[:log_dir], "#{self.name}.log"),
54
+ filename: self.log_filename,
50
55
  layout: ::Libis::Workflow::Mongoid::Config.get_log_formatter,
51
56
  level: self.log_level
52
57
  )
@@ -3,7 +3,7 @@
3
3
  module Libis
4
4
  module Workflow
5
5
  module Mongoid
6
- VERSION = '2.0.2' unless const_defined? :VERSION # the guard is against a redefinition warning that happens on Travis
6
+ VERSION = '2.0.3' unless const_defined? :VERSION # the guard is against a redefinition warning that happens on Travis
7
7
  end
8
8
  end
9
9
  end
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ require 'map_with_indifferent_access'
2
2
  require 'libis-workflow'
3
3
 
4
4
  module Libis
@@ -14,9 +14,9 @@ module Libis
14
14
 
15
15
  store_in collection: 'workflow_items'
16
16
 
17
- field :options, type: Hash, default: -> { Hash.new }
18
- field :properties, type: Hash, default: -> { Hash.new }
19
- field :summary, type: Hash, default: -> { Hash.new }
17
+ field :_options, type: Hash, default: -> { Hash.new }
18
+ field :_properties, type: Hash, default: -> { Hash.new }
19
+ field :_summary, type: Hash, default: -> { Hash.new }
20
20
 
21
21
  has_many :logs, as: :logger, class_name: Libis::Workflow::Mongoid::LogEntry.to_s,
22
22
  dependent: :destroy, autosave: true, order: :_id.asc do
@@ -39,9 +39,21 @@ module Libis
39
39
  document.logs.each { |log| log.destroy! }
40
40
  end
41
41
 
42
+ indifferent_hash :_options, :options
43
+ indifferent_hash :_properties, :properties
44
+ indifferent_hash :_summary, :summary
45
+
42
46
  end
43
47
  end
44
48
 
49
+ def to_hash
50
+ result = super
51
+ result[:options] = result.delete(:_options)
52
+ result[:properties] = result.delete(:_properties)
53
+ result[:summary] = result.delete(:_summary)
54
+ result
55
+ end
56
+
45
57
  def log_history
46
58
  # noinspection RubyResolve
47
59
  self.logs.log_history.all || []
@@ -1,5 +1,4 @@
1
- # encoding: utf-8
2
-
1
+ require 'map_with_indifferent_access'
3
2
  require 'libis/workflow/base/workflow'
4
3
  require 'libis/workflow/mongoid/base'
5
4
  require 'libis/tools/config_file'
@@ -18,12 +17,23 @@ module Libis
18
17
 
19
18
  field :name, type: String
20
19
  field :description, type: String
21
- field :config, type: Hash, default: -> { Hash.new }
20
+ field :_config, type: Hash, default: -> { Hash.new }
22
21
 
23
22
  index({name: 1}, {unique: 1})
24
23
 
25
24
  has_many :jobs, as: :workflow, dependent: :destroy, autosave: true, order: :c_at.asc
26
25
 
26
+ def self.from_hash(hash)
27
+ self.create_from_hash(hash, [:name]) do |item, cfg|
28
+ if (value = item.read_attribute(:config))
29
+ item.write_attribute(:_config, value)
30
+ item.remove_attribute(:config)
31
+ end
32
+ item.configure(cfg.key_strings_to_symbols(recursive: true).merge(name: item.name))
33
+ cfg.clear
34
+ end
35
+ end
36
+
27
37
  def self.load(file_or_hash)
28
38
  config = Libis::Tools::ConfigFile.new
29
39
  config << file_or_hash
@@ -33,6 +43,18 @@ module Libis
33
43
  workflow
34
44
  end
35
45
 
46
+ indifferent_hash :_config, :config
47
+
48
+ def to_hash
49
+ result = super
50
+ result[:config] = result.delete(:_config)
51
+ result
52
+ end
53
+
54
+ def input
55
+ Libis::Tools::DeepStruct.new(super)
56
+ end
57
+
36
58
  end
37
59
  end
38
60
  end
@@ -0,0 +1,20 @@
1
+ require "map_with_indifferent_access/version"
2
+ require "map_with_indifferent_access/wraps_collection"
3
+ require "map_with_indifferent_access/map"
4
+ require "map_with_indifferent_access/list"
5
+ require "map_with_indifferent_access/values"
6
+ require "map_with_indifferent_access/normalization"
7
+ require 'forwardable'
8
+
9
+ module MapWithIndifferentAccess
10
+ class << self
11
+ extend Forwardable
12
+
13
+ # @!method new
14
+ # Creates and returns a new instance of {Map}.
15
+ #
16
+ # @return [Map]
17
+ # @see Map#initialize
18
+ def_delegator Map, :new
19
+ end
20
+ end
@@ -0,0 +1,867 @@
1
+ module MapWithIndifferentAccess
2
+
3
+ class List
4
+ extend Forwardable
5
+ include MapWithIndifferentAccess::WrapsCollection
6
+
7
+ def first
8
+ self.at(0)
9
+ end
10
+
11
+ def last
12
+ self.at(-1)
13
+ end
14
+
15
+ # Try to convert `from_obj` into a {List}.
16
+ #
17
+ # @return [List]
18
+ # converted object if `from_obj` is convertible.
19
+ #
20
+ # @return [nil]
21
+ # if `from_obj` cannot be converted for any reason.
22
+ def self.try_convert(from_obj)
23
+ if self === from_obj
24
+ from_obj
25
+ else
26
+ array = ::Array.try_convert( from_obj )
27
+ new( array ) if array
28
+ end
29
+ end
30
+
31
+ # Try to convert `obj`, which might be a {List} into an
32
+ # `Array`.
33
+ #
34
+ # @return [Array]
35
+ # converted object if `obj` is convertible.
36
+ #
37
+ # @return [nil]
38
+ # if `obj` cannot be converted for any reason.
39
+ def self.try_deconstruct(obj)
40
+ if self === obj
41
+ obj.inner_array
42
+ elsif obj.respond_to?(:to_ary )
43
+ a = obj.to_ary
44
+ ::Array === a ? a : nil
45
+ else
46
+ nil
47
+ end
48
+ end
49
+
50
+ # @!attribute inner_array
51
+ # @return [Array]
52
+ #
53
+ # Alias for {#inner_collection}. The encapsulated `Array`
54
+ # instance.
55
+ alias inner_array inner_collection
56
+
57
+ # @!method to_a
58
+ #
59
+ # Alias for {#inner_collection}. Returns the
60
+ # encapsulated `Array` instance.
61
+
62
+ # Use class_eval to hide the aliasing from Yard doc.
63
+ class_eval 'alias to_a inner_collection', __FILE__, __LINE__
64
+
65
+ # Initializes a new instance of {List} that encapsulates a
66
+ # new empty `Array` or the `Array` coerced from the given
67
+ # `basis`.
68
+ #
69
+ # When a {List} is given as a `basis`, this results on the
70
+ # given and new instances sharing the same {#inner_array}.
71
+ # There is no obvious reason to do that on purpose, but there
72
+ # is also no particular harm in allowing it to happen.
73
+ #
74
+ # @param [Array, List, Object] basis
75
+ # An `Array` or an object that can be implicitly coerced to
76
+ # an `Array`
77
+ def initialize(basis = [])
78
+ use_basis = basis
79
+ use_basis = basis.inner_array if self.class === basis
80
+ use_basis = ::Array.try_convert( use_basis )
81
+ raise ArgumentError, "Could not convert #{basis.inspect} into an ::Array" unless use_basis
82
+ @inner_collection = use_basis
83
+ end
84
+
85
+ # Element Assignment — Sets the element at index, or replaces
86
+ # a subarray from the start index for length elements, or
87
+ # replaces a subarray specified by the range of indices.
88
+ #
89
+ # The given object or array is internalized befor being
90
+ # ussed for assignment into the {#inner_array}.
91
+ #
92
+ # @return the given value or array.
93
+ #
94
+ # @see Values.internalize
95
+ # @see #push
96
+ # @see #unshift
97
+ #
98
+ # @overload []=(index, value)
99
+ # @param index [Fixnum]
100
+ # @param value [Object]
101
+ #
102
+ # @overload []=(start, length, array_or_value)
103
+ # @param start [Fixnum]
104
+ # @param length [Fixnum]
105
+ # @param array_or_value [Array, List Object, nil]
106
+ #
107
+ # @overload []=(range, array_or_value)
108
+ # @param range [Ramge]
109
+ # @param array_or_value [Array, List, Object, nil]
110
+ def []=(index, length_or_value, *maybe_value)
111
+ arg_count = 2 + maybe_value.length
112
+ unless (2..3) === arg_count
113
+ raise ArgumentError, "wrong number of arguments (#{arg_count} for 2..3)"
114
+ end
115
+
116
+ if maybe_value.empty?
117
+ maybe_length = []
118
+ value_or_values = length_or_value
119
+ else
120
+ maybe_length = [length_or_value]
121
+ value_or_values = maybe_value.first
122
+ end
123
+
124
+ if (
125
+ ( !maybe_length.empty? || Range === index ) &&
126
+ ( value_array = List.try_deconstruct( value_or_values ) )
127
+ )
128
+ value_array = value_array.map{ |v| Values << v }
129
+ inner_array[ index, *maybe_length ] = value_array
130
+ else
131
+ value = Values << value_or_values
132
+ inner_array[ index, *maybe_length ] = value
133
+ end
134
+ end
135
+
136
+ # @!method []
137
+ # Returns the element at index, or returns a subarray
138
+ # starting at the start index and continuing for length
139
+ # elements, or returns a subarray specified by range of
140
+ # indices.
141
+ #
142
+ # Externalizes the result before returning it.
143
+ #
144
+ # @see Values.externalize
145
+ #
146
+ # @overload [](index)
147
+ # @param index [Fixnum]
148
+ # @return [Object]
149
+ #
150
+ # @overload [](start, length)
151
+ # @param start [Fixnum]
152
+ # @param length [Fixnum]
153
+ # @return [List]
154
+ #
155
+ # @overload [](range)
156
+ # @param range [Range]
157
+ # @return [List]
158
+ #
159
+ # @overload slice(index)
160
+ # @param index [Fixnum]
161
+ # @return [Object]
162
+ #
163
+ # @overload slice(start, length)
164
+ # @param start [Fixnum]
165
+ # @param length [Fixnum]
166
+ # @return [List]
167
+ #
168
+ # @overload slice(range)
169
+ # @param range [Range]
170
+ # @return [List]
171
+
172
+ ['[]', 'slice'].each do |method_name|
173
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
174
+
175
+ def #{method_name}(index, *maybe_length)
176
+ arg_count = 1 + maybe_length.length
177
+ unless (1..2) === arg_count
178
+ raise ArgumentError, "wrong number of arguments (\#{arg_count} for 1..2)"
179
+ end
180
+
181
+ if !maybe_length.empty? || Range === index
182
+ value_array = inner_array.#{method_name}( index, *maybe_length )
183
+ value_array.map!{ |v| Values >> v }
184
+ List.new( value_array )
185
+ else
186
+ value = inner_array.#{method_name}( index )
187
+ Values >> value
188
+ end
189
+ end
190
+
191
+ EOS
192
+ end
193
+
194
+ # Returns the externalization of the element at `index`. A
195
+ # negative index counts from the end of the list. Returns
196
+ # `nil` if the index is out of range.
197
+ #
198
+ # @see #[]
199
+ def at(index)
200
+ item = inner_array.at( index )
201
+ Values >> item
202
+ end
203
+
204
+ # Append. Pushes the given object on to the end of the list.
205
+ # Returns the array itself, so several appends may be chained
206
+ # together.
207
+ #
208
+ # Internalizes the given onject before appending it to the
209
+ # target's {#inner_array}.
210
+ #
211
+ # @return [List]
212
+ # @see #push
213
+ def <<(value)
214
+ value = Values << value
215
+ inner_array << value
216
+ self
217
+ end
218
+
219
+ # Append. Pushes the given object(s) on to the end of the
220
+ # list. Returns the array itself, so several appends may be
221
+ # chained together.
222
+ #
223
+ # Internalizes each given object before appending it to the
224
+ # target's {#inner_array}.
225
+ #
226
+ # @return [List]
227
+ # @see #<<
228
+ # @see #pop
229
+ # @see Values.internalize
230
+ def push(*values)
231
+ values.map!{ |v| Values << v }
232
+ inner_array.push *values
233
+ self
234
+ end
235
+
236
+ # Prepends objects to the front of the list, moving other
237
+ # elements upwards.
238
+ #
239
+ # Internalizes each value before prepending it to the
240
+ # target's {#inner_array}.
241
+ #
242
+ # See also {#shift} for the opposite effect.
243
+ #
244
+ # @return [List]
245
+ # @see #shift
246
+ # @see Values.internalize
247
+ def unshift(*values)
248
+ values.map!{ |v| Values << v }
249
+ inner_array.unshift *values
250
+ self
251
+ end
252
+
253
+ # Inserts the given values before the element with the given
254
+ # index.
255
+ #
256
+ # Internalizes the values before inserting them into the
257
+ # target's {#inner_array}.
258
+ #
259
+ # Negative indices count backwards from the end of the array,
260
+ # where -1 is the last element. If a negative index is used,
261
+ # the given values will be inserted after that element, so
262
+ # using an index of -1 will insert the values at the end of
263
+ # the list.
264
+ #
265
+ # @return [List]
266
+ # @see Values.internalize
267
+ def insert(index, *values)
268
+ values.map!{ |v| Values << v }
269
+ inner_array.insert(index, *values)
270
+ self
271
+ end
272
+
273
+ # Appends elements of `other` (a `List` or other
274
+ # `Array`-like object) to the target `List`.
275
+ #
276
+ # @param other [List, Array, Object]
277
+ # @return [List] The target list.
278
+ #
279
+ # @see #+
280
+ def concat(other)
281
+ other = self.class.try_deconstruct(other) || other
282
+ inner_array.concat other
283
+ self
284
+ end
285
+
286
+ # Returns a {List} containing the elements in self
287
+ # corresponding to the given selector(s).
288
+ #
289
+ # The selectors may be either `Integer` indices or
290
+ # `Range`s.
291
+ #
292
+ # @return List
293
+ def values_at(*indexes)
294
+ inner_result = inner_array.values_at( *indexes )
295
+ Values >> inner_result
296
+ end
297
+
298
+ # Tries to retrieve the element at position `index`, but
299
+ # raises an `IndexError` exception or uses a default value
300
+ # when an invalid index is referenced.
301
+ #
302
+ # Returns the externalization of the retrieved value.
303
+ #
304
+ # @see MapWithIndifferentAccess::Values.externalize
305
+ #
306
+ # @overload fetch(index)
307
+ # Tries to retrieve the element at position `index`, but
308
+ # raises an `IndexError` exception if the referenced index
309
+ # lies outside of the array bounds.
310
+ #
311
+ # @raise [IndexError]
312
+ #
313
+ # @overload fetch(index, default)
314
+ # Tries to retrieve the element at position `index`, but
315
+ # uses the given default if the referenced index lies
316
+ # outside of the array bounds.
317
+ #
318
+ # @overload fetch(index)
319
+ # @yieldparam index
320
+ # Tries to retrieve the element at position `index`, but if
321
+ # the referenced index lies outside of the array bounds,
322
+ # calls the given block, and uses the block call result.
323
+ def fetch(index, *args)
324
+ item =
325
+ if block_given?
326
+ inner_array.fetch( index, *args ){ |idx| yield idx }
327
+ else
328
+ inner_array.fetch( index, *args )
329
+ end
330
+ Values >> item
331
+ end
332
+
333
+ # @!method shift(*maybe_n)
334
+ # Removes and returns the first element or first `n`
335
+ # elements of the array, shifting all of the other elements
336
+ # downward.
337
+ #
338
+ # Returns the externalization of the removed element or
339
+ # array of elements
340
+ #
341
+ # See {#unshift} for the opposite effect.
342
+ #
343
+ # @see #unshift
344
+ # @see #pop
345
+ #
346
+ # @overload shift()
347
+ # Removes the first element and returns it, shifting all
348
+ # other elements down by one. Returns nil if the array is
349
+ # empty.
350
+ #
351
+ # @return [Object, nil]
352
+ #
353
+ # @overload shift(n)
354
+ # Returns a {List} of the first `n` elements (or less) just
355
+ # like `array.slice!(0, n)` does, but also removing those
356
+ # elements from the target.
357
+ #
358
+ # @return [List]
359
+
360
+ # @!method pop(*maybe_n)
361
+ # Removes and returns the last element or last `n` elements
362
+ # of the array.
363
+ #
364
+ # Returns the externalization of the removed element or array
365
+ # of elements
366
+ #
367
+ # See {#push} for the opposite effect.
368
+ #
369
+ # @see #push
370
+ # @see #shift
371
+ #
372
+ # @overload pop()
373
+ # Removes the last element and returns it. Returns nil if
374
+ # the array is empty.
375
+ #
376
+ # @return [Object, nil]
377
+ #
378
+ # @overload pop(n)
379
+ # Returns a {MapWithIndifferentAccess::List} of the last
380
+ # `n` elements (or less) just like `array.slice!(-n, n)`
381
+ # does, but also removing those elements from the target.
382
+ #
383
+ # @param n [Fixnum]
384
+ # @return [MapWithIndifferentAccess::List]
385
+ #
386
+
387
+ %w(shift pop).each do |method_name|
388
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
389
+
390
+ def #{method_name}(*maybe_n)
391
+ arg_count = maybe_n.length
392
+ unless (0..1) === arg_count
393
+ raise ArgumentError, "wrong number of arguments (\#{arg_count} for 0..1)"
394
+ end
395
+ if maybe_n.empty?
396
+ Values >> inner_array.#{method_name}
397
+ else
398
+ inner_result = inner_array.#{method_name}( *maybe_n )
399
+ List.new( inner_result )
400
+ end
401
+ end
402
+
403
+ EOS
404
+ end
405
+
406
+ # Deletes the element at the specified `index`, returning the
407
+ # externalization of that element, or `nil` if the index is
408
+ # out of range.
409
+ #
410
+ # @param index [Fixnum]
411
+ # @return [Object, nil]
412
+ #
413
+ # @see #slice
414
+ # @see Values.externalize
415
+ def delete_at(index)
416
+ inner_result = inner_array.delete_at( index )
417
+ Values >> inner_result
418
+ end
419
+
420
+ # @!method &(other)
421
+ # @param other [List, Array, Object]
422
+ # @return [List]
423
+ #
424
+ # Set Intersection. Returns a new {List} containing
425
+ # elements common to the target {List} and `other` (a
426
+ # `List` or other `Array`-like object), excluding any
427
+ # duplicate items. The order is preserved from the
428
+ # original list.
429
+ #
430
+ # It compares elements using their `#hash` and `#eql?`
431
+ # methods for efficiency.
432
+ #
433
+ # Note that this does not recongnize items of `Map` type as
434
+ # equal just because they are equal by `#==`, which can be
435
+ # the case when they have equivalent keys that differ by
436
+ # `String`/`Symbol` type. You might therefore wish to call
437
+ # {#&} for lists that have first had their keys
438
+ # deeply-stringified or deeply-symbolized.
439
+
440
+ # @!method |(other)
441
+ # @param other [List, Array, Object]
442
+ # @return [List]
443
+ #
444
+ # Set Union. Returns a new {List} by joining the target
445
+ # `List` with `other` (a `List` or other `Array`-like
446
+ # object), excluding any duplicates and preserving the
447
+ # order from the original `List`.
448
+ #
449
+ # It compares elements using their `#hash` and `#eql?`
450
+ # methods for efficiency.
451
+ #
452
+ # Note that this does not recongnize items of `Map` type as
453
+ # equal just because they are equal by `#==`, which can be
454
+ # the case when they have equivalent keys that differ by
455
+ # `String`/`Symbol` type. You might therefore wish to call
456
+ # {#|} for lists that have first had their keys
457
+ # deeply-stringified or deeply-symbolized.
458
+
459
+ # @!method +(other)
460
+ # @param other [List, Array, Object]
461
+ # @return [List]
462
+ #
463
+ # Concatenation. Returns a new {List} built by
464
+ # concatenating `other` (a `List` or other `Array`-like
465
+ # object) to the target `List`.
466
+ #
467
+ # @see #concat
468
+
469
+ # @!method -(other)
470
+ # @param other [List, Array, Object]
471
+ # @return [List]
472
+ #
473
+ # Difference. Returns a new {List} that is a copy of the
474
+ # original, removing any items that also appear in
475
+ # `other` (a `List` or other `Array`-like object). The
476
+ # order is preserved from the original `List`.
477
+ #
478
+ # It compares elements using their `#hash` and `#eql?`
479
+ # methods for efficiency.
480
+ #
481
+ # Note that this does not recongnize items of `Map` type as
482
+ # equal just because they are equal by `#==`, which can be
483
+ # the case when they have equivalent keys that differ by
484
+ # `String`/`Symbol` type. You might therefore wish to call
485
+ # {#-} for lists that have first had their keys
486
+ # deeply-stringified or deeply-symbolized.
487
+
488
+ %w( & | + - ).each do |method_name|
489
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
490
+
491
+ def #{method_name}(other)
492
+ other = self.class.try_deconstruct( other ) || other
493
+ inner_result = inner_array.#{method_name}(other)
494
+ List.new( inner_result )
495
+ end
496
+
497
+ EOS
498
+ end
499
+
500
+ # @!method join(separator=$,)
501
+ # @param separator [String]
502
+ # @return [String]
503
+ #
504
+ # Returns a string consisting of `String`-converted item
505
+ # values from the target `List` separated by the
506
+ # `separator` string. If no `separator` or `nil` is given,
507
+ # uses the value of `$,` as the separator. Treats a `nil`
508
+ # `$,` value as a blank string.
509
+ #
510
+ # The items are not externalized before being converted to
511
+ # `String`s, so `my_map.join` is exactly equivalent to
512
+ # `my_map.inner_array.join`.
513
+ #
514
+ # @see Array#join
515
+ def_delegator :inner_array, :join
516
+
517
+ # Repetition.
518
+ #
519
+ # @overload *(n_copies)
520
+ # @return [Map]
521
+ #
522
+ # Returns a new `List` built by concatenating `n_copies`
523
+ # copies of itself together.
524
+ #
525
+ # @overload *(separator)
526
+ # @return [String]
527
+ #
528
+ # Equivalent to `target_list.join(separator)`.
529
+ def *(n_copies_or_separator)
530
+ result = inner_array * n_copies_or_separator
531
+ result = List.new( result ) if Array === result
532
+ result
533
+ end
534
+
535
+ # Deletes all items from self, the externalizations of which
536
+ # are equal to the externalization of `obj`.
537
+ #
538
+ # Returns the externalization of the last deleted item if
539
+ # applicable.
540
+ #
541
+ # @see Values.externalize
542
+ #
543
+ # @overload delete(obj)
544
+ # Returns `nil` if no matching items are found.
545
+ #
546
+ # @overload delete(obj)
547
+ # @yield
548
+ # Returns the externalization of the block result is no
549
+ # matching items are found.
550
+ def delete(obj)
551
+ obj = Values >> obj
552
+ removed_items = false
553
+ result = nil
554
+ inner_array.delete_if{ |v|
555
+ v = Values >> v
556
+ if v == obj
557
+ result = v
558
+ removed_items = true
559
+ true
560
+ end
561
+ }
562
+ if !removed_items && block_given?
563
+ result = Values >> yield( obj )
564
+ end
565
+ result
566
+ end
567
+
568
+ # Returns a new instance with duplicate items omitted.
569
+ # Items are considered equal if their `#hash` values are
570
+ # equal and comparison using `#eql?` returns `true`.
571
+ #
572
+ # If a block is given, then externalized items are passed to
573
+ # the block, and the return values from the block will be
574
+ # used for dupliacte-check comparison.
575
+ #
576
+ # Note that items externally represented as `Map`s that are
577
+ # equal according to {Map#==} will not necessarily be
578
+ # identified as duplicates since they can still differ
579
+ # according to {Map#eql} if their encapsulated `Hash`
580
+ # objects are unequal due to key `String`/`Symbol` type
581
+ # differences. You might therefore want to ensure that the
582
+ # target `List` has been deeply stringified or symbolized
583
+ # before calling {#uniq!} on it.
584
+ #
585
+ # @return [List]
586
+ #
587
+ # @see #uniq!
588
+ def uniq
589
+ result = dup
590
+ if block_given?
591
+ result.uniq!{ |item| yield( item ) }
592
+ else
593
+ result.uniq!
594
+ end
595
+ result
596
+ end
597
+
598
+ # Deletes duplicate items from the target's {#inner_array},
599
+ # leaving only unique items remaining. Items are considered
600
+ # equal if their `#hash` values are equal and comparison
601
+ # using `#eql?` returns `true`.
602
+ #
603
+ # Returns the target `List` if any duplicates were found and
604
+ # removed. Otherwise, returns `nil`.
605
+ #
606
+ # If a block is given, then externalized items are passed to
607
+ # the block, and the return values from the block will be
608
+ # used for dupliacte-check comparison.
609
+ #
610
+ # Note that items externally represented as `Map`s that are
611
+ # equal according to {Map#==} will not necessarily be
612
+ # identified as duplicates since they can still differ
613
+ # according to {Map#eql} if their encapsulated `Hash`
614
+ # objects are unequal due to key `String`/`Symbol` type
615
+ # differences. You might therefore want to ensure that the
616
+ # target `List` has been deeply stringified or symbolized
617
+ # before calling {#uniq} on it.
618
+ #
619
+ # @return [List, nil]
620
+ #
621
+ # @see #uniq
622
+ def uniq!
623
+ inner_result =
624
+ if block_given?
625
+ inner_array.uniq!{ |item|
626
+ yield( Values >> item )
627
+ }
628
+ else
629
+ inner_array.uniq!
630
+ end
631
+
632
+ inner_result && self
633
+ end
634
+
635
+ # Equality. The target is equal to the given `Array`-like
636
+ # object if both contain the same number of elements, and
637
+ # externalizations of corresponding items in itself and the
638
+ # given object are equal according to `#==`.
639
+ #
640
+ # @return [Boolean]
641
+ #
642
+ # @see Values.externalize
643
+ def ==(other)
644
+ same_class = self.class === other
645
+
646
+ return false unless same_class || other.respond_to?(:to_ary )
647
+
648
+ # Optimizations
649
+ return true if equal?( other )
650
+ return true if same_class && inner_array == other.inner_array
651
+
652
+ return false unless length == other.length
653
+ zip( other ).all? { |(v,other_v)| v == Values >> other_v }
654
+ end
655
+
656
+ # @param [List, Array, Object]
657
+ # @return [1, 0, -1, nil]
658
+ #
659
+ # Comparison. Returns an integer (-1, 0, or +1) if this
660
+ # `List` is less than, equal to, or greater than `other`, and
661
+ # `other` is a `List` or other `Array`-like object that can
662
+ # be coerced to a `List`.
663
+ #
664
+ # Each externaized item in the target `List` is compared to
665
+ # the corresponding externalized item in `other` (using the
666
+ # `<=>` operator). As soon as a comparison is non zero (i.e.
667
+ # the two corresponding elements are not equal), that result
668
+ # is returned for the whole array comparison.
669
+ #
670
+ # If all the elements are equal, then the result is based on
671
+ # a comparison of the list lengths. Thus, two `List`s are
672
+ # "equal" according to {#<=>} if, and only if, they have the
673
+ # same length and the value of each element is equal to the
674
+ # value of the corresponding element in the other list.
675
+ #
676
+ # `nil` is returned if `other` is not a `List` or `Array`like
677
+ # object or if the comparison of two elements returns `nil`.
678
+ #
679
+ # @see Array#<=>
680
+ def <=>(other)
681
+ return nil unless \
682
+ List === other || (
683
+ other.respond_to?(:to_ary ) && other.respond_to?(:length )
684
+ )
685
+ other = Values >> other
686
+ rel_order( other )
687
+ end
688
+
689
+ # Calls the given block once for each item in the target's
690
+ # {#inner_array}, passing the externalization of the item to
691
+ # the block.
692
+ #
693
+ # @see MapWithIndifferentAccess::Values.externalize
694
+ #
695
+ # @overload each
696
+ # @yieldparam item
697
+ # @return [List]
698
+ #
699
+ # @overload each
700
+ # @return [Enumerator]
701
+ def each
702
+ inner_array.each do |item|
703
+ item = Values >> item
704
+ yield item
705
+ end
706
+ end
707
+
708
+ # @!method assoc(value)
709
+ # Searches through elements of the target `List` that are
710
+ # also externally represented as `List`s, comparing the
711
+ # first item in each of those with `value` using {#==}.
712
+ #
713
+ # Returns the first item from the target that matches (is
714
+ # the first associated `List`) or nil of no match is found.
715
+ #
716
+ # @return [List, nil]
717
+ #
718
+ # @see Array#assoc
719
+ # @see #rassoc
720
+
721
+ # @!method rassoc(value)
722
+ # Searches through elements of the target `List` that are
723
+ # also externally represented as `List`s, comparing the
724
+ # second item in each of those with `value` using {#==}.
725
+ #
726
+ # Returns the first item from the target that matches (is
727
+ # the first associated `List`) or nil of no match is found.
728
+ #
729
+ # @return [List, nil]
730
+ #
731
+ # @see Array#rassoc
732
+ # @see #assoc
733
+
734
+ [ ['assoc', 0 ], ['rassoc', 1 ] ].each do |(method_name,search_col)|
735
+ class_eval <<-EOS, __FILE__, __LINE__
736
+
737
+ def #{method_name}(value)
738
+ result = nil
739
+ each do |item|
740
+ next unless List === item && item.length > #{search_col}
741
+ result = item if item[#{search_col}] == value
742
+ end
743
+ result
744
+ end
745
+
746
+ EOS
747
+ end
748
+
749
+ # Works identically to `Array#bsearch` except that
750
+ # externalized values are passed to the block, and the
751
+ # externalized result is returned.
752
+ #
753
+ # @overload bsearch
754
+ # @yieldparam x
755
+ # @return [Object, nil]
756
+ #
757
+ # @overload bsearch
758
+ # @return [Enumerator]
759
+ def bsearch
760
+ return to_enum(:bsearch) unless block_given?
761
+ inner_result = inner_array.bsearch{ |x| yield Values >> x }
762
+ Values >> inner_result
763
+ end
764
+
765
+ # @!method collect!
766
+ # Invokes the given block once for each externalized item
767
+ # from the target `List`, replacing the element with the
768
+ # internalization of the value returned by the block.
769
+ #
770
+ # If no block is given, returns an `Enumerator` instead.
771
+ #
772
+ # @yieldparam extern_item
773
+ # @return [List, Enumerable]
774
+ #
775
+ # @see Enumerable#collect
776
+ #
777
+ # @overload collect!
778
+ # @overload map!
779
+
780
+ %w(collect! map!).each do |method_name|
781
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
782
+
783
+ def #{method_name}
784
+ return to_enum( :#{method_name} ) unless block_given?
785
+
786
+ inner_array.#{method_name}{ |item|
787
+ item = Values >> item
788
+ mapped_outer = yield( item )
789
+ Values << mapped_outer
790
+ }
791
+ self
792
+ end
793
+
794
+ EOS
795
+ end
796
+
797
+ # Yields every combination of length n of elements from the
798
+ # target `List` in the form of a `List` and then returns the
799
+ # target `List` itself.
800
+ #
801
+ # Makes no guarantees about the order in which the
802
+ # combinations are yielded.
803
+ #
804
+ # If no block is given, an `Enumerator` is returned instead.
805
+ #
806
+ # @overload combination(n)
807
+ # @param n [Fixnum]
808
+ # @yieldparam combination [List]
809
+ # @return [List]
810
+ #
811
+ # @overload combination(n)
812
+ # @param n [Fixnum]
813
+ # @return [Enumerator]
814
+ def combination(n)
815
+ return to_enum( :combination, n ) unless block_given?
816
+
817
+ inner_array.combination n do |inner_combos|
818
+ yield List.new( inner_combos )
819
+ end
820
+ self
821
+ end
822
+
823
+ # Removes `nil` elements from the target `List`.
824
+ #
825
+ # Returns `nil` if no changes were made. Otherwise returns
826
+ # the `List`.
827
+ #
828
+ # @return [List, nil]
829
+ #
830
+ # @see #compact
831
+ def compact!
832
+ inner_array.compact! && self
833
+ end
834
+
835
+ # Returns a copy of the target `List` with all `nil` items
836
+ # removed.
837
+ #
838
+ # @return [List]
839
+ #
840
+ # @see #compact!
841
+ def compact
842
+ result = dup
843
+ result.compact!
844
+ result
845
+ end
846
+
847
+ protected
848
+
849
+ def rel_order(other_list)
850
+ length_rel = length <=> other_list.length
851
+ return other_list.reverse_rel_order(self) if length_rel == 1
852
+
853
+ rel = 0
854
+ zip other_list do |a,b|
855
+ rel = a <=> b
856
+ break unless rel == 0
857
+ end
858
+ rel == 0 ? length_rel : rel
859
+ end
860
+
861
+ def reverse_rel_order(other_list)
862
+ rel = rel_order(other_list)
863
+ rel.nil? ? nil : -rel
864
+ end
865
+ end
866
+
867
+ end