berkeley_library-tind 0.4.0 → 0.5.0
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/.github/workflows/build.yml +1 -1
- data/.idea/inspectionProfiles/Project_Default.xml +18 -0
- data/.idea/tind.iml +91 -91
- data/.ruby-version +1 -1
- data/CHANGES.md +33 -1
- data/README.md +15 -1
- data/berkeley_library-tind.gemspec +3 -2
- data/lib/berkeley_library/tind/api/api.rb +17 -11
- data/lib/berkeley_library/tind/api/collection.rb +1 -1
- data/lib/berkeley_library/tind/api/search.rb +2 -2
- data/lib/berkeley_library/tind/export/exporter.rb +1 -1
- data/lib/berkeley_library/tind/export/table.rb +1 -1
- data/lib/berkeley_library/tind/export/table_metrics.rb +1 -1
- data/lib/berkeley_library/tind/marc/xml_builder.rb +62 -0
- data/lib/berkeley_library/tind/marc/xml_reader.rb +32 -19
- data/lib/berkeley_library/tind/marc/xml_writer.rb +152 -0
- data/lib/berkeley_library/tind/module_info.rb +1 -1
- data/lib/berkeley_library/util/files.rb +39 -0
- data/lib/berkeley_library/util/ods/spreadsheet.rb +1 -1
- data/lib/berkeley_library/util/ods/xml/element_node.rb +1 -1
- data/spec/berkeley_library/tind/export/export_spec.rb +3 -1
- data/spec/berkeley_library/tind/export/table_spec.rb +2 -0
- data/spec/berkeley_library/tind/marc/xml_reader_spec.rb +42 -1
- data/spec/berkeley_library/tind/marc/xml_writer_spec.rb +156 -0
- data/spec/data/new-records.xml +46 -0
- metadata +36 -39
- data/Jenkinsfile +0 -18
- data/lib/berkeley_library/util/arrays.rb +0 -178
- data/lib/berkeley_library/util/logging.rb +0 -1
- data/lib/berkeley_library/util/paths.rb +0 -111
- data/lib/berkeley_library/util/stringios.rb +0 -30
- data/lib/berkeley_library/util/strings.rb +0 -42
- data/lib/berkeley_library/util/sys_exits.rb +0 -15
- data/lib/berkeley_library/util/times.rb +0 -22
- data/lib/berkeley_library/util/uris/appender.rb +0 -162
- data/lib/berkeley_library/util/uris/requester.rb +0 -62
- data/lib/berkeley_library/util/uris/validator.rb +0 -32
- data/lib/berkeley_library/util/uris.rb +0 -44
- data/spec/berkeley_library/util/arrays_spec.rb +0 -340
- data/spec/berkeley_library/util/paths_spec.rb +0 -90
- data/spec/berkeley_library/util/stringios_spec.rb +0 -34
- data/spec/berkeley_library/util/strings_spec.rb +0 -27
- data/spec/berkeley_library/util/times_spec.rb +0 -39
- data/spec/berkeley_library/util/uris_spec.rb +0 -118
@@ -1,162 +0,0 @@
|
|
1
|
-
require 'berkeley_library/util/paths'
|
2
|
-
require 'uri'
|
3
|
-
require 'typesafe_enum'
|
4
|
-
|
5
|
-
module BerkeleyLibrary
|
6
|
-
module Util
|
7
|
-
module URIs
|
8
|
-
|
9
|
-
# Appends the specified paths to the path of the specified URI, removing any extraneous slashes,
|
10
|
-
# and builds a new URI with that path and the same scheme, host, query, fragment, etc.
|
11
|
-
# as the original.
|
12
|
-
class Appender
|
13
|
-
attr_reader :original_uri, :elements
|
14
|
-
|
15
|
-
# Creates and invokes a new {Appender}.
|
16
|
-
#
|
17
|
-
# @param uri [URI, String] the original URI
|
18
|
-
# @param elements [Array<String, Symbol>] the URI elements to join.
|
19
|
-
# @raise URI::InvalidComponentError if appending the specified elements would create an invalid URI
|
20
|
-
def initialize(uri, *elements)
|
21
|
-
raise ArgumentError, 'uri cannot be nil' unless (@original_uri = URIs.uri_or_nil(uri))
|
22
|
-
|
23
|
-
@elements = elements.map(&:to_s)
|
24
|
-
@elements.each_with_index do |element, elem_index|
|
25
|
-
next start_query_at(elem_index) if element.include?('?')
|
26
|
-
next start_fragment_at(elem_index) if element.include?('#')
|
27
|
-
|
28
|
-
add_element(element)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Returns the new URI.
|
33
|
-
#
|
34
|
-
# @return [URI] a new URI appending the joined path elements.
|
35
|
-
# @raise URI::InvalidComponentError if appending the specified elements would create an invalid URI
|
36
|
-
def to_uri
|
37
|
-
original_uri.dup.tap do |new_uri|
|
38
|
-
new_uri.path = Paths.join(original_uri.path, *path_elements)
|
39
|
-
new_uri.query = query unless query_elements.empty?
|
40
|
-
new_uri.fragment = fragment unless fragment_elements.empty?
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def state
|
47
|
-
@state ||= :path
|
48
|
-
end
|
49
|
-
|
50
|
-
def in_query?
|
51
|
-
state == :query
|
52
|
-
end
|
53
|
-
|
54
|
-
def in_fragment?
|
55
|
-
state == :fragment
|
56
|
-
end
|
57
|
-
|
58
|
-
def query
|
59
|
-
query_elements.join
|
60
|
-
end
|
61
|
-
|
62
|
-
def fragment
|
63
|
-
fragment_elements.join
|
64
|
-
end
|
65
|
-
|
66
|
-
def path_elements
|
67
|
-
@path_elements ||= []
|
68
|
-
end
|
69
|
-
|
70
|
-
def query_elements
|
71
|
-
@query_elements ||= [].tap { |e| e << original_uri.query if original_uri.query }
|
72
|
-
end
|
73
|
-
|
74
|
-
def fragment_elements
|
75
|
-
@fragment_elements ||= [].tap { |e| e << original_uri.fragment if original_uri.fragment }
|
76
|
-
end
|
77
|
-
|
78
|
-
def start_query_at(elem_index)
|
79
|
-
raise URI::InvalidComponentError, err_query_after_fragment(elem_index) if in_fragment?
|
80
|
-
raise URI::InvalidComponentError, err_too_many_queries(elem_index) unless query_elements.empty?
|
81
|
-
|
82
|
-
handle_query_start(elem_index)
|
83
|
-
@state = :query
|
84
|
-
end
|
85
|
-
|
86
|
-
def start_fragment_at(elem_index)
|
87
|
-
raise URI::InvalidComponentError, err_too_many_fragments(elem_index) unless fragment_elements.empty?
|
88
|
-
raise URI::InvalidComponentError, err_query_after_fragment(elem_index) if query_after_fragment?(elem_index)
|
89
|
-
|
90
|
-
handle_fragment_start(elem_index)
|
91
|
-
@state = :fragment
|
92
|
-
end
|
93
|
-
|
94
|
-
def query_after_fragment?(elem_index)
|
95
|
-
e = elements[elem_index]
|
96
|
-
e.index('?', e.index('#'))
|
97
|
-
end
|
98
|
-
|
99
|
-
def add_element(e)
|
100
|
-
return fragment_elements << e if in_fragment?
|
101
|
-
return query_elements << e if in_query? || (e.include?('&') && !query_elements.empty?)
|
102
|
-
|
103
|
-
path_elements << e
|
104
|
-
end
|
105
|
-
|
106
|
-
def handle_query_start(elem_index)
|
107
|
-
element = elements[elem_index]
|
108
|
-
|
109
|
-
# if there's anything before the '?', we treat that excess as a path element
|
110
|
-
excess, q_start = split_around(element, element.index('?'))
|
111
|
-
q_start = push_fragment_start(elem_index, q_start)
|
112
|
-
|
113
|
-
query_elements << q_start
|
114
|
-
path_elements << excess
|
115
|
-
end
|
116
|
-
|
117
|
-
# if the fragment starts in the middle of this element, we keep the part before
|
118
|
-
# the fragment delimiter '#', and push the rest (w/'#') back onto the next element
|
119
|
-
# to be parsed in the next iteration
|
120
|
-
def push_fragment_start(elem_index, q_start)
|
121
|
-
return q_start unless (f_index = q_start.index('#'))
|
122
|
-
|
123
|
-
next_index = elem_index + 1
|
124
|
-
q_start, q_next = split_around(q_start, f_index) # NOTE: this doesn't return the '#'
|
125
|
-
elements[next_index] = "##{q_next}#{elements[next_index]}" # so we prepend one here
|
126
|
-
q_start
|
127
|
-
end
|
128
|
-
|
129
|
-
def handle_fragment_start(elem_index)
|
130
|
-
element = elements[elem_index]
|
131
|
-
|
132
|
-
# if there's anything before the '#', we treat that excess as a path element,
|
133
|
-
# or as a query element if there's a query
|
134
|
-
excess, f_start = split_around(element, element.index('#'))
|
135
|
-
|
136
|
-
fragment_elements << f_start
|
137
|
-
if in_query?
|
138
|
-
query_elements << excess
|
139
|
-
else
|
140
|
-
path_elements << excess
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
def split_around(s, i)
|
145
|
-
[s[0...i], s[(i + 1)..]]
|
146
|
-
end
|
147
|
-
|
148
|
-
def err_too_many_queries(elem_index)
|
149
|
-
"#{elements[elem_index].inspect}: URI already has a query string: #{query.inspect}"
|
150
|
-
end
|
151
|
-
|
152
|
-
def err_query_after_fragment(elem_index)
|
153
|
-
"#{elements[elem_index].inspect}: Query delimiter '?' cannot follow fragment delimeter '#'"
|
154
|
-
end
|
155
|
-
|
156
|
-
def err_too_many_fragments(elem_index)
|
157
|
-
"#{elements[elem_index].inspect}: URI already has a fragment: #{fragment.inspect}"
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'rest-client'
|
2
|
-
require 'berkeley_library/util/uris/appender'
|
3
|
-
require 'berkeley_library/util/uris/validator'
|
4
|
-
require 'berkeley_library/logging'
|
5
|
-
|
6
|
-
module BerkeleyLibrary
|
7
|
-
module Util
|
8
|
-
module URIs
|
9
|
-
module Requester
|
10
|
-
class << self
|
11
|
-
include BerkeleyLibrary::Logging
|
12
|
-
|
13
|
-
# Performs a GET request.
|
14
|
-
#
|
15
|
-
# @param uri [URI, String] the URI to GET
|
16
|
-
# @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
|
17
|
-
# @param headers [Hash] the request headers.
|
18
|
-
# @return [String] the body as a string.
|
19
|
-
# @raise [RestClient::Exception] in the event of an error.
|
20
|
-
def get(uri, params = {}, headers = {})
|
21
|
-
url_str = url_str_with_params(uri, params)
|
22
|
-
resp = get_or_raise(url_str, headers)
|
23
|
-
resp.body
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def url_str_with_params(uri, params)
|
29
|
-
raise ArgumentError, 'uri cannot be nil' unless (url_str = Validator.url_str_or_nil(uri))
|
30
|
-
|
31
|
-
elements = [].tap do |ee|
|
32
|
-
ee << url_str
|
33
|
-
ee << '?' unless url_str.include?('?')
|
34
|
-
ee << URI.encode_www_form(params)
|
35
|
-
end
|
36
|
-
|
37
|
-
uri = Appender.new(*elements).to_uri
|
38
|
-
uri.to_s
|
39
|
-
end
|
40
|
-
|
41
|
-
def get_or_raise(url_str, headers)
|
42
|
-
resp = RestClient.get(url_str, headers)
|
43
|
-
begin
|
44
|
-
return resp if (status = resp.code) == 200
|
45
|
-
|
46
|
-
raise(exception_for(resp, status))
|
47
|
-
ensure
|
48
|
-
logger.info("GET #{url_str} returned #{status}")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def exception_for(resp, status)
|
53
|
-
RestClient::RequestFailed.new(resp, status).tap do |ex|
|
54
|
-
status_message = RestClient::STATUSES[status] || '(Unknown)'
|
55
|
-
ex.message = "#{status} #{status_message}"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
|
-
module BerkeleyLibrary
|
4
|
-
module Util
|
5
|
-
module URIs
|
6
|
-
module Validator
|
7
|
-
class << self
|
8
|
-
|
9
|
-
# Returns the specified URL as a URI.
|
10
|
-
# @param url [String, URI] the URL.
|
11
|
-
# @return [URI] the URI.
|
12
|
-
# @raise [URI::InvalidURIError] if `url` cannot be parsed as a URI.
|
13
|
-
def uri_or_nil(url)
|
14
|
-
return unless url
|
15
|
-
|
16
|
-
# noinspection RubyYardReturnMatch
|
17
|
-
url.is_a?(URI) ? url : URI.parse(url.to_s)
|
18
|
-
end
|
19
|
-
|
20
|
-
# Returns the specified URL as a string.
|
21
|
-
# @param url [String, URI] the URL.
|
22
|
-
# @return [String] the URL.
|
23
|
-
# @raise [URI::InvalidURIError] if `url` cannot be parsed as a URI.
|
24
|
-
def url_str_or_nil(url)
|
25
|
-
uri = Validator.uri_or_nil(url)
|
26
|
-
uri.to_s if uri
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'berkeley_library/util/uris/appender'
|
2
|
-
require 'berkeley_library/util/uris/requester'
|
3
|
-
require 'berkeley_library/util/uris/validator'
|
4
|
-
|
5
|
-
module BerkeleyLibrary
|
6
|
-
module Util
|
7
|
-
module URIs
|
8
|
-
class << self
|
9
|
-
include URIs
|
10
|
-
end
|
11
|
-
|
12
|
-
# Appends the specified paths to the path of the specified URI, removing any extraneous slashes
|
13
|
-
# and merging additional query parameters, and returns a new URI with that path and the same scheme,
|
14
|
-
# host, query, fragment, etc. as the original.
|
15
|
-
#
|
16
|
-
# @param uri [URI, String] the original URI
|
17
|
-
# @param elements [Array<String, Symbol>] the URI elements to join.
|
18
|
-
# @return [URI] a new URI appending the joined path elements.
|
19
|
-
# @raise URI::InvalidComponentError if appending the specified elements would create an invalid URI
|
20
|
-
def append(uri, *elements)
|
21
|
-
Appender.new(uri, *elements).to_uri
|
22
|
-
end
|
23
|
-
|
24
|
-
# Performs a GET request.
|
25
|
-
#
|
26
|
-
# @param uri [URI, String] the URI to GET
|
27
|
-
# @param params [Hash] the query parameters to add to the URI. (Note that the URI may already include query parameters.)
|
28
|
-
# @param headers [Hash] the request headers.
|
29
|
-
# @return [String] the body as a string.
|
30
|
-
# @raise [RestClient::Exception] in the event of an error.
|
31
|
-
def get(uri, params = {}, headers = {})
|
32
|
-
Requester.get(uri, params, headers)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Returns the specified URL as a URI.
|
36
|
-
# @param url [String, URI] the URL.
|
37
|
-
# @return [URI] the URI.
|
38
|
-
# @raise [URI::InvalidURIError] if `url` cannot be parsed as a URI.
|
39
|
-
def uri_or_nil(url)
|
40
|
-
Validator.uri_or_nil(url)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,340 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
require 'berkeley_library/util/arrays'
|
4
|
-
|
5
|
-
module BerkeleyLibrary::Util
|
6
|
-
describe Arrays do
|
7
|
-
describe :ordered_superset do
|
8
|
-
let(:sup) { %w[a b c d e] }
|
9
|
-
|
10
|
-
it 'returns true for an identical subset' do
|
11
|
-
expect(Arrays.ordered_superset?(superset: sup, subset: sup.dup)).to eq(true)
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'returns true for an empty subset' do
|
15
|
-
expect(Arrays.ordered_superset?(superset: sup, subset: [])).to eq(true)
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'returns true for an exact sublist' do
|
19
|
-
subs = [
|
20
|
-
%w[a b c],
|
21
|
-
%w[b c d],
|
22
|
-
%w[c d e]
|
23
|
-
]
|
24
|
-
subs.each do |sub|
|
25
|
-
expect(Arrays.ordered_superset?(superset: sup, subset: sub)).to eq(true)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'returns true when the superset interpolates extra elements' do
|
30
|
-
subs = [
|
31
|
-
%w[a c e],
|
32
|
-
%w[b d],
|
33
|
-
%w[a b d e]
|
34
|
-
]
|
35
|
-
subs.each do |sub|
|
36
|
-
expect(Arrays.ordered_superset?(superset: sup, subset: sub)).to eq(true)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'returns false for a too-large subset' do
|
41
|
-
sub = %w[a b c d e f g]
|
42
|
-
expect(Arrays.ordered_superset?(superset: sup, subset: sub)).to eq(false)
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'returns false when extra elements are present' do
|
46
|
-
subs = [
|
47
|
-
%w[a b c x],
|
48
|
-
%w[x b c d],
|
49
|
-
%w[c d x e]
|
50
|
-
]
|
51
|
-
subs.each do |sub|
|
52
|
-
expect(Arrays.ordered_superset?(superset: sup, subset: sub)).to eq(false)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
describe :count_while do
|
58
|
-
it 'returns the count of matching elements' do
|
59
|
-
a = [1, 3, 5, 2, 4, 6, 7, 11, 13]
|
60
|
-
expect(Arrays.count_while(values: a, &:odd?)).to eq(3)
|
61
|
-
end
|
62
|
-
|
63
|
-
it 'returns 0 if the first element does not match' do
|
64
|
-
a = [2, 4, 6, 7, 11, 13]
|
65
|
-
expect(Arrays.count_while(values: a, &:odd?)).to eq(0)
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'returns an enumerator if not passed a block' do
|
69
|
-
a = [1, 3, 5, 2, 4, 6, 7, 11, 13]
|
70
|
-
e = Arrays.count_while(values: a)
|
71
|
-
expect(e.each(&:odd?)).to eq(3)
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'works on non-arrays' do
|
75
|
-
a = [1, 3, 5, 2, 4, 6, 7, 11, 13]
|
76
|
-
e = Enumerator.new { |y| a.each { |x| y << x } }
|
77
|
-
expect(Arrays.count_while(values: e, &:odd?)).to eq(3)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
describe :find_indices do
|
82
|
-
let(:target) { %w[a b c d e] }
|
83
|
-
|
84
|
-
it 'returns identity indices for an identical subset' do
|
85
|
-
expect(Arrays.find_indices(for_array: target.dup, in_array: target)).to eq([0, 1, 2, 3, 4])
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'returns an empty array for an empty subset' do
|
89
|
-
expect(Arrays.find_indices(for_array: [], in_array: target)).to eq([])
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'returns the expected subindices for an exact sublist' do
|
93
|
-
sources = {
|
94
|
-
%w[a b c] => [0, 1, 2],
|
95
|
-
%w[b c d] => [1, 2, 3],
|
96
|
-
%w[c d e] => [2, 3, 4]
|
97
|
-
}
|
98
|
-
sources.each do |source, expected|
|
99
|
-
expect(Arrays.find_indices(for_array: source, in_array: target)).to eq(expected)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'returns nil for a too-large subset' do
|
104
|
-
source = %w[a b c d e f g]
|
105
|
-
expect(Arrays.find_indices(for_array: source, in_array: target)).to be_nil
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'returns nil when extra elements are present' do
|
109
|
-
sources = [
|
110
|
-
%w[a b c x],
|
111
|
-
%w[x b c d],
|
112
|
-
%w[c d x e]
|
113
|
-
]
|
114
|
-
sources.each do |source|
|
115
|
-
expect(Arrays.find_indices(for_array: source, in_array: target)).to be_nil
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
it 'takes a comparison block' do
|
120
|
-
sub = %i[a c e]
|
121
|
-
expect(Arrays.find_indices(for_array: sub, in_array: target) { |source, target| target == source.to_s }).to eq([0, 2, 4])
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
describe :find_index do
|
126
|
-
let(:arr) { [0, 2, 4, 6, 4] }
|
127
|
-
|
128
|
-
it 'finds an index based on a value' do
|
129
|
-
expect(Arrays.find_index(4, in_array: arr)).to eq(2)
|
130
|
-
expect(Arrays.find_index(4, in_array: arr, start_index: 3)).to eq(4)
|
131
|
-
end
|
132
|
-
|
133
|
-
it 'finds an index based on a block' do
|
134
|
-
expect(Arrays.find_index(in_array: arr) { |x| x > 3 }).to eq(2)
|
135
|
-
expect(Arrays.find_index(in_array: arr, start_index: 3) { |x| x < 5 }).to eq(4)
|
136
|
-
end
|
137
|
-
|
138
|
-
it 'returns nil if no equal value found' do
|
139
|
-
expect(Arrays.find_index(7, in_array: arr)).to be_nil
|
140
|
-
expect(Arrays.find_index(2, in_array: arr, start_index: 2)).to be_nil
|
141
|
-
end
|
142
|
-
|
143
|
-
it 'returns nil if no matching value found' do
|
144
|
-
expect(Arrays.find_index(in_array: arr, &:odd?)).to be_nil
|
145
|
-
expect(Arrays.find_index(in_array: arr, start_index: 2) { |x| x < 4 }).to be_nil
|
146
|
-
end
|
147
|
-
|
148
|
-
# rubocop:disable Lint/Void
|
149
|
-
it 'returns an enumerator if given no arguments' do
|
150
|
-
e = Arrays.find_index(in_array: arr)
|
151
|
-
expect(e.each { |x| x > 3 }).to eq(2)
|
152
|
-
|
153
|
-
e = Arrays.find_index(in_array: arr, start_index: 3)
|
154
|
-
expect(e.each { |x| x < 5 }).to eq(4)
|
155
|
-
end
|
156
|
-
# rubocop:enable Lint/Void
|
157
|
-
end
|
158
|
-
|
159
|
-
describe :merge do
|
160
|
-
it 'merges two arrays' do
|
161
|
-
a1 = [1, 2, 3]
|
162
|
-
a2 = [2, 3, 4]
|
163
|
-
expect(Arrays.merge(a1, a2)).to eq([1, 2, 3, 4])
|
164
|
-
expect(Arrays.merge(a2, a1)).to eq([1, 2, 3, 4])
|
165
|
-
end
|
166
|
-
|
167
|
-
it 'merges disjoint arrays' do
|
168
|
-
a1 = [1, 3, 5]
|
169
|
-
a2 = [2, 4, 6]
|
170
|
-
expect(Arrays.merge(a1, a2)).to eq([1, 3, 5, 2, 4, 6])
|
171
|
-
end
|
172
|
-
|
173
|
-
it 'preserves duplicates' do
|
174
|
-
a1 = [1, 2, 2, 3, 4, 5]
|
175
|
-
a2 = [2, 4, 4, 5, 5, 6]
|
176
|
-
|
177
|
-
merged = Arrays.merge(a1, a2)
|
178
|
-
[a1, a2].each do |a|
|
179
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
180
|
-
end
|
181
|
-
|
182
|
-
expected = [1, 2, 2, 3, 4, 4, 5, 5, 6]
|
183
|
-
expect(merged.size).to eq(expected.size)
|
184
|
-
expect(merged).to eq(expected)
|
185
|
-
end
|
186
|
-
|
187
|
-
it 'merges gappy arrays' do
|
188
|
-
a1 = [1, 4, 5, 7, 9]
|
189
|
-
a2 = [2, 3, 4, 7, 8, 9]
|
190
|
-
|
191
|
-
merged = Arrays.merge(a1, a2)
|
192
|
-
[a1, a2].each do |a|
|
193
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
194
|
-
end
|
195
|
-
|
196
|
-
expected = [1, 2, 3, 4, 5, 7, 8, 9]
|
197
|
-
expect(merged.size).to eq(expected.size)
|
198
|
-
expect(merged).to eq(expected)
|
199
|
-
end
|
200
|
-
|
201
|
-
it 'preserves order when merging arrays with duplicates' do
|
202
|
-
a1 = [1, 3, 2, 2, 4]
|
203
|
-
a2 = [1, 2, 3, 2, 4]
|
204
|
-
|
205
|
-
merged = Arrays.merge(a1, a2)
|
206
|
-
[a1, a2].each do |a|
|
207
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
208
|
-
end
|
209
|
-
|
210
|
-
expected = [1, 2, 3, 2, 2, 4]
|
211
|
-
expect(merged.size).to eq(expected.size)
|
212
|
-
expect(merged).to eq(expected)
|
213
|
-
end
|
214
|
-
|
215
|
-
it 'preserves nil' do
|
216
|
-
a1 = [1, 3, nil, nil, 4]
|
217
|
-
a2 = [1, nil, 3, nil, 4]
|
218
|
-
|
219
|
-
merged = Arrays.merge(a1, a2)
|
220
|
-
[a1, a2].each do |a|
|
221
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
222
|
-
end
|
223
|
-
|
224
|
-
expected = [1, nil, 3, nil, nil, 4]
|
225
|
-
expect(merged.size).to eq(expected.size)
|
226
|
-
expect(merged).to eq(expected)
|
227
|
-
end
|
228
|
-
|
229
|
-
it 'works with non-comparable types' do
|
230
|
-
a1 = [1, 3, 'two', 'two', 4]
|
231
|
-
a2 = [1, 'two', 3, 'two', 4]
|
232
|
-
|
233
|
-
merged = Arrays.merge(a1, a2)
|
234
|
-
[a1, a2].each do |a|
|
235
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
236
|
-
end
|
237
|
-
|
238
|
-
expected = [1, 'two', 3, 'two', 'two', 4]
|
239
|
-
expect(merged.size).to eq(expected.size)
|
240
|
-
expect(merged).to eq(expected)
|
241
|
-
end
|
242
|
-
|
243
|
-
it 'returns the larger array if the smaller is already a subarray' do
|
244
|
-
a1 = [2, 3, 4]
|
245
|
-
a2 = [1, 2, 3, 4, 5]
|
246
|
-
|
247
|
-
merged = Arrays.merge(a1, a2)
|
248
|
-
[a1, a2].each do |a|
|
249
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
250
|
-
end
|
251
|
-
|
252
|
-
expected = a2
|
253
|
-
expect(merged.size).to eq(expected.size)
|
254
|
-
expect(merged).to eq(expected)
|
255
|
-
|
256
|
-
expect(Arrays.merge(a2, a1)).to eq(expected)
|
257
|
-
end
|
258
|
-
|
259
|
-
it 'sorts where sorting preserves order' do
|
260
|
-
a1 = [1, 2, 3, 4, 5]
|
261
|
-
a2 = [2, 3, 6, 9]
|
262
|
-
|
263
|
-
merged = Arrays.merge(a1, a2)
|
264
|
-
[a1, a2].each do |a|
|
265
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
266
|
-
end
|
267
|
-
|
268
|
-
expected = [1, 2, 3, 4, 5, 6, 9]
|
269
|
-
expect(merged.size).to eq(expected.size)
|
270
|
-
expect(merged).to eq(expected)
|
271
|
-
|
272
|
-
expect(Arrays.merge(a2, a1)).to eq(expected)
|
273
|
-
end
|
274
|
-
|
275
|
-
it "doesn't muck up partial matches" do
|
276
|
-
a1 = [1, 2, 3, 4, 5]
|
277
|
-
a2 = [6, 9, 2, 3]
|
278
|
-
|
279
|
-
merged = Arrays.merge(a1, a2)
|
280
|
-
[a1, a2].each do |a|
|
281
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
282
|
-
end
|
283
|
-
|
284
|
-
expected = [1, 6, 9, 2, 3, 4, 5]
|
285
|
-
expect(merged.size).to eq(expected.size)
|
286
|
-
expect(merged).to eq(expected)
|
287
|
-
|
288
|
-
expect(Arrays.merge(a2, a1)).to eq(expected)
|
289
|
-
end
|
290
|
-
|
291
|
-
it "doesn't much up disjoints" do
|
292
|
-
a1 = [1, 2, 3, 4, 5]
|
293
|
-
a2 = [6, 9]
|
294
|
-
|
295
|
-
merged = Arrays.merge(a1, a2)
|
296
|
-
[a1, a2].each do |a|
|
297
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
298
|
-
end
|
299
|
-
|
300
|
-
expected = [1, 2, 3, 4, 5, 6, 9]
|
301
|
-
expect(merged.size).to eq(expected.size)
|
302
|
-
expect(merged).to eq(expected)
|
303
|
-
|
304
|
-
expect(Arrays.merge(a2, a1)).to eq(expected)
|
305
|
-
end
|
306
|
-
|
307
|
-
it 'works on a selection of random values' do
|
308
|
-
next_int = ->(n) { (n * rand).to_i }
|
309
|
-
rand_array = -> { (0...next_int.call(10)).map { next_int.call(10) } }
|
310
|
-
aggregate_failures 'random values' do
|
311
|
-
100.times do
|
312
|
-
a1 = rand_array.call
|
313
|
-
a2 = rand_array.call
|
314
|
-
|
315
|
-
merged = Arrays.merge(a1, a2)
|
316
|
-
[a1, a2].each do |a|
|
317
|
-
expect(Arrays.ordered_superset?(superset: merged, subset: a)).to eq(true), "merge(#{[a1.join, a2.join].inspect}): #{a.join} not found in #{merged.join}"
|
318
|
-
end
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
end
|
324
|
-
|
325
|
-
describe :invert do
|
326
|
-
it 'inverts an array of ints' do
|
327
|
-
expect(Arrays.invert([0, 2, 3])).to eq([0, nil, 1, 2])
|
328
|
-
end
|
329
|
-
|
330
|
-
it 'fails if values are not ints' do
|
331
|
-
# noinspection RubyYardParamTypeMatch
|
332
|
-
expect { Arrays.invert(%i[a b c]) }.to raise_error(TypeError)
|
333
|
-
end
|
334
|
-
|
335
|
-
it 'fails if given duplicate values' do
|
336
|
-
expect { Arrays.invert([1, 2, 3, 2]) }.to raise_error(ArgumentError)
|
337
|
-
end
|
338
|
-
end
|
339
|
-
end
|
340
|
-
end
|