berkeley_library-util 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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