berkeley_library-util 0.1.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +18 -0
  3. data/.gitignore +240 -0
  4. data/.idea/.gitignore +8 -0
  5. data/.idea/inspectionProfiles/Project_Default.xml +21 -0
  6. data/.idea/misc.xml +6 -0
  7. data/.idea/modules.xml +8 -0
  8. data/.idea/util.iml +123 -0
  9. data/.idea/vcs.xml +6 -0
  10. data/.rubocop.yml +334 -0
  11. data/.ruby-version +1 -0
  12. data/.simplecov +8 -0
  13. data/.yardopts +1 -0
  14. data/CHANGES.md +3 -0
  15. data/Gemfile +3 -0
  16. data/LICENSE.md +21 -0
  17. data/README.md +21 -0
  18. data/Rakefile +20 -0
  19. data/berkeley_library-util.gemspec +42 -0
  20. data/lib/berkeley_library/util/arrays.rb +178 -0
  21. data/lib/berkeley_library/util/module_info.rb +16 -0
  22. data/lib/berkeley_library/util/paths.rb +111 -0
  23. data/lib/berkeley_library/util/stringios.rb +30 -0
  24. data/lib/berkeley_library/util/strings.rb +42 -0
  25. data/lib/berkeley_library/util/sys_exits.rb +15 -0
  26. data/lib/berkeley_library/util/times.rb +22 -0
  27. data/lib/berkeley_library/util/uris/appender.rb +162 -0
  28. data/lib/berkeley_library/util/uris/requester.rb +62 -0
  29. data/lib/berkeley_library/util/uris/validator.rb +32 -0
  30. data/lib/berkeley_library/util/uris.rb +44 -0
  31. data/lib/berkeley_library/util.rb +1 -0
  32. data/rakelib/bundle.rake +8 -0
  33. data/rakelib/coverage.rake +11 -0
  34. data/rakelib/gem.rake +54 -0
  35. data/rakelib/rubocop.rake +18 -0
  36. data/rakelib/spec.rake +2 -0
  37. data/spec/.rubocop.yml +40 -0
  38. data/spec/berkeley_library/util/arrays_spec.rb +340 -0
  39. data/spec/berkeley_library/util/paths_spec.rb +90 -0
  40. data/spec/berkeley_library/util/stringios_spec.rb +34 -0
  41. data/spec/berkeley_library/util/strings_spec.rb +59 -0
  42. data/spec/berkeley_library/util/times_spec.rb +39 -0
  43. data/spec/berkeley_library/util/uris/requester_spec.rb +75 -0
  44. data/spec/berkeley_library/util/uris/validator_spec.rb +51 -0
  45. data/spec/berkeley_library/util/uris_spec.rb +133 -0
  46. data/spec/spec_helper.rb +30 -0
  47. metadata +321 -0
data/.rubocop.yml ADDED
@@ -0,0 +1,334 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+ - Include
5
+
6
+ # Allow one line around block body (Layout/EmptyLines will still disallow two or more)
7
+ Layout/EmptyLinesAroundBlockBody:
8
+ Enabled: false
9
+
10
+ # Allow one line around class body (Layout/EmptyLines will still disallow two or more)
11
+ Layout/EmptyLinesAroundClassBody:
12
+ Enabled: false
13
+
14
+ # Allow one line around module body (Layout/EmptyLines will still disallow two or more)
15
+ Layout/EmptyLinesAroundModuleBody:
16
+ Enabled: false
17
+
18
+ # Reasonable line-length check; it's too easy for the cure to be worse than the disease
19
+ Layout/LineLength:
20
+ Max: 150
21
+
22
+ # Make indents consistent regardless of the lengths of variables and method names and whatnot
23
+ Layout/MultilineMethodCallIndentation:
24
+ EnforcedStyle: indented
25
+
26
+ # Produces monsters
27
+ Layout/MultilineOperationIndentation:
28
+ Enabled: false
29
+
30
+ # We meant to do that
31
+ Naming/MemoizedInstanceVariableName:
32
+ Enabled: False
33
+
34
+ # It works in context, trust us
35
+ Naming/MethodParameterName:
36
+ Enabled: False
37
+
38
+ # Confusing and weird
39
+ Naming/VariableNumber:
40
+ Enabled: False
41
+
42
+ # Do what's readable in the context you're in
43
+ Style/AccessModifierDeclarations:
44
+ Enabled: false
45
+
46
+ # 👎 to cultural imperialism
47
+ Style/AsciiComments:
48
+ Enabled: false
49
+
50
+ # Seriously?
51
+ Style/CommentedKeyword:
52
+ Enabled: False
53
+
54
+ # Disable problematic module documentation check (see https://github.com/bbatsov/rubocop/issues/947)
55
+ Style/Documentation:
56
+ Enabled: false
57
+
58
+ # Adding more line noise to format strings will not improve them
59
+ Style/FormatStringToken:
60
+ Enabled: false
61
+
62
+ # Putting '# frozen_string_literal: true' everywhere does not make the world a better place
63
+ Style/FrozenStringLiteralComment:
64
+ Enabled: false
65
+
66
+ # Requiring the lambda() method just makes wordy calls wordier
67
+ Style/Lambda:
68
+ EnforcedStyle: literal
69
+
70
+ # `foo.positive?` is cute, but it's not actually more readable than `foo > 0`
71
+ Style/NumericPredicate:
72
+ Enabled: false
73
+
74
+ # Unclear why it's a good idea to give parameters semantically meaningless names
75
+ Style/SingleLineBlockParams:
76
+ Enabled: false
77
+
78
+ # The semantics of `foo&.bar` are a lot less interchangeable with `foo && foo.bar` than RuboCop thinks
79
+ Style/SafeNavigation:
80
+ Enabled: false
81
+
82
+ # Short arrays are often more readable as plain arrays
83
+ Style/WordArray:
84
+ MinSize: 3
85
+
86
+ ############################################################
87
+ # Added in RuboCop 0.80
88
+
89
+ Style/HashEachMethods:
90
+ Enabled: true
91
+
92
+ Style/HashTransformKeys:
93
+ Enabled: true
94
+
95
+ Style/HashTransformValues:
96
+ Enabled: true
97
+
98
+ ############################################################
99
+ # Added in RuboCop 0.81
100
+
101
+ Lint/StructNewOverride:
102
+ Enabled: true
103
+
104
+ Lint/RaiseException:
105
+ Enabled: true
106
+
107
+ ############################################################
108
+ # Added in RuboCop 0.82
109
+
110
+ Layout/SpaceAroundMethodCallOperator:
111
+ Enabled: true
112
+
113
+ Style/ExponentialNotation:
114
+ Enabled: false
115
+
116
+ ############################################################
117
+ # Added in RuboCop 0.83
118
+
119
+ Layout/EmptyLinesAroundAttributeAccessor:
120
+ Enabled: true
121
+
122
+ Style/SlicingWithRange:
123
+ Enabled: true
124
+
125
+ ############################################################
126
+ # Added in RuboCop 0.84
127
+
128
+ Lint/DeprecatedOpenSSLConstant:
129
+ Enabled: true
130
+
131
+ ############################################################
132
+ # Added in RuboCop 0.85
133
+
134
+ Lint/MixedRegexpCaptureTypes:
135
+ Enabled: true
136
+
137
+ Style/RedundantRegexpEscape:
138
+ Enabled: true
139
+
140
+ Style/RedundantRegexpCharacterClass:
141
+ Enabled: true
142
+
143
+ ############################################################
144
+ # Added in Rubocop 0.86
145
+
146
+ Style/RedundantFetchBlock:
147
+ Enabled: true
148
+
149
+ ############################################################
150
+ # Added in Rubocop 0.87
151
+
152
+ # Sometimes we separate things for a reason
153
+ Style/AccessorGrouping:
154
+ Enabled: false
155
+
156
+ Style/BisectedAttrAccessor:
157
+ Enabled: true
158
+
159
+ Style/RedundantAssignment:
160
+ Enabled: true
161
+
162
+ ############################################################
163
+ # Added in Rubocop 0.88
164
+
165
+ Lint/DuplicateElsifCondition:
166
+ Enabled: true
167
+
168
+ Style/ArrayCoercion:
169
+ Enabled: true
170
+
171
+ Style/CaseLikeIf:
172
+ Enabled: true
173
+
174
+ Style/HashAsLastArrayItem:
175
+ Enabled: true
176
+
177
+ Style/HashLikeCase:
178
+ Enabled: true
179
+
180
+ Style/RedundantFileExtensionInRequire:
181
+ Enabled: true
182
+
183
+ ############################################################
184
+ # Added in Rubocop 0.89
185
+
186
+ Lint/BinaryOperatorWithIdenticalOperands:
187
+ Enabled: true
188
+
189
+ Lint/DuplicateRescueException:
190
+ Enabled: true
191
+
192
+ Lint/EmptyConditionalBody:
193
+ Enabled: true
194
+
195
+ Lint/FloatComparison:
196
+ Enabled: true
197
+
198
+ Lint/MissingSuper:
199
+ Enabled: true
200
+
201
+ Lint/OutOfRangeRegexpRef:
202
+ Enabled: true
203
+
204
+ Lint/SelfAssignment:
205
+ Enabled: true
206
+
207
+ Lint/TopLevelReturnWithArgument:
208
+ Enabled: true
209
+
210
+ Lint/UnreachableLoop:
211
+ Enabled: true
212
+
213
+ Style/ExplicitBlockArgument:
214
+ Enabled: true
215
+
216
+ Style/GlobalStdStream:
217
+ Enabled: true
218
+
219
+ Style/OptionalBooleanParameter:
220
+ Enabled: true
221
+
222
+ Style/SingleArgumentDig:
223
+ Enabled: true
224
+
225
+ Style/SoleNestedConditional:
226
+ Enabled: true
227
+
228
+ Style/StringConcatenation:
229
+ Enabled: true
230
+
231
+ ############################################################
232
+ # Added in Rubocop 0.90
233
+
234
+ Lint/DuplicateRequire:
235
+ Enabled: true
236
+
237
+ Lint/EmptyFile:
238
+ Enabled: true
239
+
240
+ Lint/TrailingCommaInAttributeDeclaration:
241
+ Enabled: true
242
+
243
+ Lint/UselessMethodDefinition:
244
+ Enabled: true
245
+
246
+ Style/CombinableLoops:
247
+ Enabled: true
248
+
249
+ Style/KeywordParametersOrder:
250
+ Enabled: true
251
+
252
+ Style/RedundantSelfAssignment:
253
+ Enabled: true
254
+
255
+ ############################################################
256
+ # Added in Rubocop 0.91
257
+
258
+ Layout/BeginEndAlignment:
259
+ Enabled: true
260
+
261
+ Lint/ConstantDefinitionInBlock:
262
+ Enabled: true
263
+
264
+ Lint/IdentityComparison:
265
+ Enabled: true
266
+
267
+ Lint/UselessTimes:
268
+ Enabled: true
269
+
270
+ ############################################################
271
+ # Added in Rubocop 1.1–1.9
272
+
273
+ Layout/SpaceBeforeBrackets: # (new in 1.7)
274
+ Enabled: true
275
+ Lint/AmbiguousAssignment: # (new in 1.7)
276
+ Enabled: true
277
+ Lint/DeprecatedConstants: # (new in 1.8)
278
+ Enabled: true
279
+ Lint/DuplicateBranch: # (new in 1.3)
280
+ Enabled: true
281
+ Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1)
282
+ Enabled: true
283
+ Lint/EmptyBlock: # (new in 1.1)
284
+ Enabled: true
285
+ Lint/EmptyClass: # (new in 1.3)
286
+ Enabled: true
287
+ Lint/LambdaWithoutLiteralBlock: # (new in 1.8)
288
+ Enabled: true
289
+ Lint/NoReturnInBeginEndBlocks: # (new in 1.2)
290
+ Enabled: true
291
+ Lint/NumberedParameterAssignment: # (new in 1.9)
292
+ Enabled: true
293
+ Lint/OrAssignmentToConstant: # (new in 1.9)
294
+ Enabled: true
295
+ Lint/RedundantDirGlobSort: # (new in 1.8)
296
+ Enabled: true
297
+ Lint/SymbolConversion: # (new in 1.9)
298
+ Enabled: true
299
+ Lint/ToEnumArguments: # (new in 1.1)
300
+ Enabled: true
301
+ Lint/TripleQuotes: # (new in 1.9)
302
+ Enabled: true
303
+ Lint/UnexpectedBlockArity: # (new in 1.5)
304
+ Enabled: true
305
+ Lint/UnmodifiedReduceAccumulator: # (new in 1.1)
306
+ Enabled: true
307
+ Style/ArgumentsForwarding: # (new in 1.1)
308
+ Enabled: true
309
+ Style/CollectionCompact: # (new in 1.2)
310
+ Enabled: true
311
+ Style/DocumentDynamicEvalDefinition: # (new in 1.1)
312
+ Enabled: true
313
+ Style/EndlessMethod: # (new in 1.8)
314
+ Enabled: true
315
+ Style/HashExcept: # (new in 1.7)
316
+ Enabled: true
317
+ Style/IfWithBooleanLiteralBranches: # (new in 1.9)
318
+ Enabled: true
319
+ Style/NegatedIfElseCondition: # (new in 1.2)
320
+ Enabled: true
321
+ Style/NilLambda: # (new in 1.3)
322
+ Enabled: true
323
+ Style/RedundantArgument: # (new in 1.4)
324
+ Enabled: true
325
+ Style/SwapValues: # (new in 1.1)
326
+ Enabled: true
327
+
328
+ ############################################################
329
+ # Added in RuboCop 1.10
330
+
331
+ Gemspec/DateAssignment: # (new in 1.10)
332
+ Enabled: true
333
+ Style/HashConversion: # (new in 1.10)
334
+ Enabled: true
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.4
data/.simplecov ADDED
@@ -0,0 +1,8 @@
1
+ require 'simplecov-rcov'
2
+
3
+ SimpleCov.start 'rails' do
4
+ add_filter 'module_info.rb'
5
+ coverage_dir 'artifacts'
6
+ formatter SimpleCov::Formatter::RcovFormatter
7
+ minimum_coverage 100
8
+ end
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private --protected lib/**/*.rb -m markdown
data/CHANGES.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.0 (2021-09-23)
2
+
3
+ Initial extraction of code from [`berkeley_library/tind`](https://github.com/BerkeleyLibrary/tind).
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # The MIT License (MIT)
2
+
3
+ Copyright © 2021 The Regents of the University of California
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a
6
+ copy of this software and associated documentation files (the “Software”),
7
+ to deal in the Software without restriction, including without limitation
8
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
+ and/or sell copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # BerkeleyLibrary::Util
2
+
3
+ [![Build Status](https://github.com/BerkeleyLibrary/util/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/BerkeleyLibrary/util/actions/workflows/build.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/berkeley_library-util.svg)](https://github.com/BerkeleyLibrary/util/releases)
5
+
6
+ Miscellaneous utility code for the UC Berkeley Library.
7
+
8
+ ### Notable modules
9
+
10
+ ## [BerkeleyLibrary::Util::Arrays](lib/berkeley_library/util/arrays.rb)]
11
+
12
+ Routines for comparing, merging, and examining the contents of arrays.
13
+
14
+ ## [BerkeleyLibrary::Util::Paths](lib/berkeley_library/util/paths.rb)]
15
+
16
+ Routines for modifying paths separated by forward slashes,
17
+ (such as URL paths), modeled on the [Go `path` package](https://golang.org/pkg/path/).
18
+
19
+ ## [BerkeleyLibrary::Util::URIs](lib/berkeley_library/util/uris.rb)]
20
+
21
+ Routines for constructing, validating, and retrieving the content from URIs.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('Gemfile', __dir__)
2
+ require 'bundler/setup' # Set up gems listed in the Gemfile.
3
+
4
+ # ------------------------------------------------------------
5
+ # Application code
6
+
7
+ File.expand_path('lib', __dir__).tap do |lib|
8
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
9
+ end
10
+
11
+ # ------------------------------------------------------------
12
+ # CI
13
+
14
+ ENV['RAILS_ENV'] = 'test' if ENV['CI']
15
+
16
+ # ------------------------------------------------------------
17
+ # Custom tasks
18
+
19
+ desc 'Run tests, check test coverage, check code style, check for vulnerabilities, build gem'
20
+ task default: %i[coverage rubocop bundle:audit gem]
@@ -0,0 +1,42 @@
1
+ File.expand_path('lib', __dir__).tap do |lib|
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ end
4
+
5
+ ruby_version = '>= 2.7'
6
+
7
+ require 'berkeley_library/util/module_info'
8
+
9
+ Gem::Specification.new do |spec|
10
+ spec.name = BerkeleyLibrary::Util::ModuleInfo::NAME
11
+ spec.author = BerkeleyLibrary::Util::ModuleInfo::AUTHOR
12
+ spec.email = BerkeleyLibrary::Util::ModuleInfo::AUTHOR_EMAIL
13
+ spec.summary = BerkeleyLibrary::Util::ModuleInfo::SUMMARY
14
+ spec.description = BerkeleyLibrary::Util::ModuleInfo::DESCRIPTION
15
+ spec.license = BerkeleyLibrary::Util::ModuleInfo::LICENSE
16
+ spec.version = BerkeleyLibrary::Util::ModuleInfo::VERSION
17
+ spec.homepage = BerkeleyLibrary::Util::ModuleInfo::HOMEPAGE
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.required_ruby_version = ruby_version
24
+
25
+ spec.add_dependency 'berkeley_library-logging', '~> 0.2'
26
+ spec.add_dependency 'rest-client', '~> 2.1'
27
+ spec.add_dependency 'typesafe_enum', '~> 0.3'
28
+
29
+ spec.add_development_dependency 'bundle-audit', '~> 0.1'
30
+ spec.add_development_dependency 'ci_reporter_rspec', '~> 1.0'
31
+ spec.add_development_dependency 'colorize', '~> 0.8'
32
+ spec.add_development_dependency 'dotenv', '~> 2.7'
33
+ spec.add_development_dependency 'rake', '~> 13.0'
34
+ spec.add_development_dependency 'rspec', '~> 3.10'
35
+ spec.add_development_dependency 'rubocop', '= 1.11'
36
+ spec.add_development_dependency 'rubocop-rake', '~> 0.5'
37
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.2'
38
+ spec.add_development_dependency 'ruby-prof', '~> 0.17.0'
39
+ spec.add_development_dependency 'simplecov', '~> 0.21'
40
+ spec.add_development_dependency 'simplecov-rcov', '~> 0.2'
41
+ spec.add_development_dependency 'webmock', '~> 3.12'
42
+ end
@@ -0,0 +1,178 @@
1
+ module BerkeleyLibrary
2
+ module Util
3
+ module Arrays
4
+ class << self
5
+ # Clients can chose to call class methods directly, or include the module
6
+ include Arrays
7
+ end
8
+
9
+ # Recursively checks whether the specified list contains, in the
10
+ # same order, all values in the other specified list (additional codes
11
+ # in between are fine)
12
+ #
13
+ # @param subset [Array] the values to look for
14
+ # @param superset [Array] the list of values to look in
15
+ # @return boolean True if all values were found, false otherwise
16
+ def ordered_superset?(superset:, subset:)
17
+ !find_indices(in_array: superset, for_array: subset).nil?
18
+ end
19
+
20
+ # Counts how many contiguous elements from the start of an
21
+ # sequence of values satisfy the given block.
22
+ #
23
+ # @overload count_while(arr:)
24
+ # Returns an enumerator.
25
+ # @param values [Enumerable] the values
26
+ # @return [Enumerator] the enumerator.
27
+ # @overload count_while(arr:, &block)
28
+ # Passes elements to the block until the block returns nil or false,
29
+ # then stops iterating and returns the count of matching elements.
30
+ # @param values [Enumerable] the values
31
+ # @return [Integer] the count
32
+ def count_while(values:)
33
+ return to_enum(:count_while, values: values) unless block_given?
34
+
35
+ values.inject(0) do |count, x|
36
+ matched = yield x
37
+ break count unless matched
38
+
39
+ count + 1
40
+ end
41
+ end
42
+
43
+ # Given two lists, one of which is a superset of the other, with elements
44
+ # in the same order (but possibly with additional elements in the superset),
45
+ # returns an array the length of the subset, containing for each element in
46
+ # the subset the index of the corresponding element in the superset.
47
+ #
48
+ # @overload find_matching_indices(for_array:, in_array:)
49
+ # For each value in `for_array`, finds the index of the first equal value
50
+ # in `in_array` after the previously matched value.
51
+ # @param in_array [Array] the list of values to look in
52
+ # @param for_array [Array] the values to look for
53
+ # @return [Array<Integer>, nil] the indices in `in_array` of each value in `for_array`,
54
+ # or `nil` if not all values could be found
55
+ #
56
+ # @overload find_matching_indices(for_array:, in_array:)
57
+ # For each value in `for_array`, finds the index of the first value
58
+ # in `in_array` after the previously matched value that matches
59
+ # the specified match function.
60
+ # @param in_array [Array] the list of values to look in
61
+ # @param for_array [Array] the values to look for
62
+ # @yieldparam source [Object] the value to compare
63
+ # @yieldparam target [Object] the value to compare against
64
+ # @return [Array<Integer>, nil] the indices in `in_array` of each value in `for_array`,
65
+ # or `nil` if not all values could be found
66
+ def find_indices(for_array:, in_array:, &block)
67
+ return find_indices_matching(for_array, in_array, &block) if block_given?
68
+
69
+ find_all_indices(for_array, in_array)
70
+ end
71
+
72
+ # Given a block or a value, finds the index of the first matching value
73
+ # at or after the specified start index.
74
+ #
75
+ # @overload find_index(value, in_array:, start_index:)
76
+ # Finds the first index of the specified value.
77
+ # @param value [Object] the value to find
78
+ # @param in_array [Array] the array to search
79
+ # @param start_index [Integer] the index to start with
80
+ # @return [Integer, nil] the index, or `nil` if no value matches
81
+ # @overload find_index(&block)
82
+ # Finds the index of the first value matching
83
+ # the specified block.
84
+ # @param in_array [Array] the array to search
85
+ # @param start_index [Integer] the index to start with
86
+ # @yieldreturn [Boolean] whether the element matches
87
+ # @return [Integer, nil] the index, or `nil` if no value matches
88
+ # @overload find_index
89
+ # @param in_array [Array] the array to search
90
+ # @param start_index [Integer] the index to start with
91
+ # @return [Enumerator] a new enumerator
92
+ def find_index(*args, in_array:, start_index: 0, &block)
93
+ raise ArgumentError, "wrong number of arguments (given #{value.length}, expected 0..1" if args.size > 1
94
+ return Enumerator.new { |y| find_index(in_array: in_array, start_index: start_index, &y) } if args.empty? && !block_given?
95
+ return unless (relative_index = in_array[start_index..].find_index(*args, &block))
96
+
97
+ relative_index + start_index
98
+ end
99
+
100
+ # Given an array of unique integers _a<sub>1</sub>_, returns a new array
101
+ # _a<sub>2</sub>_ in which the value at each index _i<sub>2</sub>_ is the
102
+ # index _i<sub>1</sub>_ at which that value was found in _a<sub>1</sub>_.
103
+ # E.g., given `[0, 2, 3]`, returns `[0, nil, 1, 2]`. The indices need
104
+ # not be in order but must be unique.
105
+ #
106
+ # @param arr [Array<Integer>, nil] the array to invert.
107
+ # @return [Array<Integer, nil>, nil] the inverted array, or nil if the input array is nil
108
+ # @raise TypeError if `arr` is not an array of integers
109
+ # @raise ArgumentError if `arr` contains duplicate values
110
+ def invert(arr)
111
+ return unless arr
112
+
113
+ # noinspection RubyNilAnalysis
114
+ Array.new(arr.size).tap do |inv|
115
+ arr.each_with_index do |v, i|
116
+ next inv[v] = i unless (prev_index = inv[v])
117
+
118
+ raise ArgumentError, "Duplicate value #{v} at index #{i} already found at #{prev_index}"
119
+ end
120
+ end
121
+ end
122
+
123
+ # Merges two arrays in an order-preserving manner.
124
+ # @param a1 [Array] the first array
125
+ # @param a2 [Array] the second array
126
+ # @return [Array] a merged array that is an ordered superset of both `a1` and `a2`
127
+ # @see Arrays#ordered_superset?
128
+ def merge(a1, a2)
129
+ return a1 if a2.empty?
130
+ return a2 if a1.empty?
131
+
132
+ shorter, longer = a1.size > a2.size ? [a2, a1] : [a1, a2]
133
+ do_merge(shorter, longer)
134
+ end
135
+
136
+ private
137
+
138
+ def do_merge(shorter, longer)
139
+ shorter.each_with_index do |v, ix_s|
140
+ next unless (ix_l = longer.find_index(v))
141
+
142
+ shorter_unmatched = shorter[0...ix_s]
143
+ longer_unmatched = longer[0...ix_l]
144
+ all_unmatched = sort_by_first_and_flatten(shorter_unmatched, longer_unmatched)
145
+ return (all_unmatched << v) + merge(shorter[ix_s + 1..], longer[ix_l + 1..])
146
+ end
147
+
148
+ sort_by_first_and_flatten(longer, shorter)
149
+ end
150
+
151
+ def sort_by_first_and_flatten(a1, a2)
152
+ return a1 if a2.empty?
153
+ return a2 if a1.empty?
154
+ return a2 + a1 if a1.first.respond_to?(:>) && a1.first > a2.first
155
+
156
+ a1 + a2
157
+ end
158
+
159
+ def find_all_indices(source, target)
160
+ source.each_with_object([]) do |src, target_indices|
161
+ target_offset = (target_indices.last&.+ 1) || 0
162
+ return nil unless (target_index = find_index(src, in_array: target, start_index: target_offset))
163
+
164
+ target_indices << target_index
165
+ end
166
+ end
167
+
168
+ def find_indices_matching(source, target)
169
+ source.each_with_object([]) do |src, target_indices|
170
+ target_offset = (target_indices.last&.+ 1) || 0
171
+ return nil unless (target_index = find_index(in_array: target, start_index: target_offset) { |tgt| yield src, tgt })
172
+
173
+ target_indices << target_index
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,16 @@
1
+ module BerkeleyLibrary
2
+ module Util
3
+ class ModuleInfo
4
+ NAME = 'berkeley_library-util'.freeze
5
+ AUTHOR = 'David Moles'.freeze
6
+ AUTHOR_EMAIL = 'dmoles@berkeley.edu'.freeze
7
+ SUMMARY = 'Miscellaneous Ruby utilities for the UC Berkeley Library'.freeze
8
+ DESCRIPTION = <<~DESC.gsub(/\s+/, ' ').strip
9
+ A collection of miscellaneous Ruby routines for the UC Berkeley Library.
10
+ DESC
11
+ LICENSE = 'MIT'.freeze
12
+ VERSION = '0.1.0'.freeze
13
+ HOMEPAGE = 'https://github.com/BerkeleyLibrary/util'.freeze
14
+ end
15
+ end
16
+ end