libis-workflow-mongoid 2.0.2 → 2.0.3
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.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/lib/libis/workflow/mongoid/base.rb +42 -3
- data/lib/libis/workflow/mongoid/job.rb +25 -3
- data/lib/libis/workflow/mongoid/run.rb +9 -4
- data/lib/libis/workflow/mongoid/version.rb +1 -1
- data/lib/libis/workflow/mongoid/work_item_base.rb +16 -4
- data/lib/libis/workflow/mongoid/workflow.rb +25 -3
- data/lib/map_with_indifferent_access.rb +20 -0
- data/lib/map_with_indifferent_access/list.rb +867 -0
- data/lib/map_with_indifferent_access/map.rb +833 -0
- data/lib/map_with_indifferent_access/normalization.rb +92 -0
- data/lib/map_with_indifferent_access/normalization/deep_normalizer.rb +104 -0
- data/lib/map_with_indifferent_access/values.rb +39 -0
- data/lib/map_with_indifferent_access/version.rb +3 -0
- data/lib/map_with_indifferent_access/wraps_collection.rb +152 -0
- data/libis-workflow-mongoid.gemspec +0 -1
- data/spec/items/test_dir_item.rb +1 -1
- data/spec/items/test_file_item.rb +3 -3
- data/spec/tasks/camelize_name.rb +1 -1
- data/spec/workflow_spec.rb +2 -2
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae15d66c11b105c5d81e707bad684d3b7f9805ec
|
4
|
+
data.tar.gz: 1116380cbcc99ae1bf7ba0aac642f12c633feac9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 082fee48cc81d1a54b0e451b93ab4704aa86aaaf8d479946bfbeeff886eabee7628b43730266c027f08b61fcfaf6eb7ec11c2bf27c60f5dfffc33f0fb79f50da
|
7
|
+
data.tar.gz: 4563f3ec708ecc6e72d05e1b7485422dae39b06080a7afa5ddba4be5df6481ea9aeac5387dc4e234ca54549fab37da23e71709008534244f4c7ae0096848abfe
|
data/Gemfile
CHANGED
@@ -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
|
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
|
-
|
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 :
|
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:
|
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.
|
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
|
-
|
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 :
|
18
|
-
field :
|
19
|
-
field :
|
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
|
-
|
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 :
|
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
|