invoca-utils 0.1.1 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 415752871460b9b105317298a448748ab21d3e66
4
- data.tar.gz: f946ff261cbc342c34d3a82da9c1d4bb31328317
2
+ SHA256:
3
+ metadata.gz: bc2c9db5fc433f7e4f0abfc7ba11ca6a41065e5a4e9a233bb6f38fa8d310e0d0
4
+ data.tar.gz: 426e3b56dcb95d79b964d613198b2c483050da092999a4ccd4e29348231b0588
5
5
  SHA512:
6
- metadata.gz: dc7d2f1aa0a27e6afcdbcb32fecf90dd39e987145ef5d0f875711cc8631e676bc9551f8d761710426d3d7a96343c03e1b25a3a4fba0fc93b0fa610815385375f
7
- data.tar.gz: ed4627548537d4c8d8d79c177a605f45920f494aa3e747b5b5585801e4837ebaf88db6c48035ad59bd4d7d89a87ba2eccf1c88028803036cc5c2262cb1ab68a5
6
+ metadata.gz: b0e762c4b3a2e044abc0a340dc741a9258b2a3afbca3caf575f5f160067fef863ce10d40b03bc374275d0953c9984fd0bf2253df8d5cdcc0e43774bbf42948fe
7
+ data.tar.gz: fc973c0be3d37b5f8e38f2e12d9ea45a87539c78ff8afe7b7f8adf8351d623f847af68a1d9df96bbc39938894dfb6e2bf2fe4f36ef182278f80b3511381cdedf
data/.gitignore CHANGED
@@ -10,10 +10,12 @@ coverage
10
10
  doc/
11
11
  lib/bundler/man
12
12
  pkg
13
+ pkg/
13
14
  rdoc
14
- spec/reports
15
+ test/reports
15
16
  test/tmp
16
17
  test/version_tmp
18
+
17
19
  tmp
18
20
  *.bundle
19
21
  *.so
@@ -22,4 +24,3 @@ tmp
22
24
  mkmf.log
23
25
  .idea/
24
26
  .rubocop-http*
25
- test/reports
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/groovy
2
+ @Library('jenkins-pipeline@v0.4.5')
3
+ import com.invoca.docker.*;
4
+ pipeline {
5
+ agent {
6
+ kubernetes {
7
+ defaultContainer "ruby"
8
+ yamlFile ".jenkins/ruby_build_pod.yml"
9
+ }
10
+ }
11
+
12
+ environment {
13
+ GITHUB_TOKEN = credentials('github_token')
14
+ BUNDLE_GEM__FURY__IO = credentials('gemfury_deploy_token')
15
+ }
16
+
17
+ stages {
18
+ stage('Setup') {
19
+ steps {
20
+ script {
21
+ sh 'bundle install'
22
+ }
23
+ }
24
+ }
25
+ stage('Unit Test') {
26
+ steps {
27
+ script {
28
+ sh 'bundle exec rake'
29
+ }
30
+ }
31
+ post {
32
+ always { junit '*/reports/*.xml' }
33
+ success { updateGitHubStatus('clean-build', 'success', 'Unit tests.') }
34
+ failure { updateGitHubStatus('clean-build', 'failure', 'Unit tests.') }
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ void updateGitHubStatus(String context, String status, String description) {
41
+ gitHubStatus([
42
+ repoSlug: 'Invoca/invoca-utils',
43
+ sha: env.GIT_COMMIT,
44
+ description: description,
45
+ context: context,
46
+ targetURL: env.BUILD_URL,
47
+ token: env.GITHUB_TOKEN,
48
+ status: status
49
+ ])
50
+ }
@@ -0,0 +1,19 @@
1
+ ---
2
+ apiVersion: v1
3
+ kind: Pod
4
+ metadata:
5
+ labels:
6
+ jenkins/invoca-utils: 'true'
7
+ namespace: jenkins
8
+ name: invoca-utils
9
+ spec:
10
+ containers:
11
+ - name: ruby
12
+ image: ruby:2.6.5
13
+ tty: true
14
+ resources:
15
+ requests:
16
+ memory: "100Mi"
17
+ command:
18
+ - cat
19
+
@@ -1 +1 @@
1
- 2.4.2
1
+ 2.6.5
@@ -0,0 +1,32 @@
1
+ # CHANGELOG for `invoca-utils`
2
+
3
+ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
+
5
+ Note: This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.4.1] - 2020-06-17
8
+ ### Fixed
9
+ - Support Ruby < 2.5 by adding `begin`/`end` around `rescue` in `retry_on_exception`.
10
+
11
+ ## [0.4.0] - 2020-06-09
12
+ ### Added
13
+ - Added `Invoca::Utils.retry_on_exception`.
14
+ - Added `Invoca::Utils::GuaranteedUTF8String.normalize_all_strings` to normalize
15
+ all strings found in a JSON doc of hashes, arrays, and values.
16
+
17
+ ## [0.3.0] - 2020-04-28
18
+ ### Added
19
+ - Array::* operator changed to use alias_method instead of prepend to prevent infinite recursion when HoboSupport gem is present
20
+ - Enumerable::map_and_find, map_with_index, and map_hash methods ported from HoboSupport
21
+ - Hash::select_hash, map_hash, partition_hash, & and - methods ported from HoboSupport
22
+ - HashWithIndifferentAccess::partition_hash, & and - methods ported from HoboSupport
23
+ - Module::alias_method_chain ported from HoboSupport
24
+
25
+ ## [0.2.0] - 2020-04-27
26
+ ### Added
27
+ - Enumerable::build_hash method ported from HoboSupport
28
+ - Enumerable::* operator ported from HoboSupport
29
+
30
+ [0.4.0]: https://github.com/Invoca/invoca-utils/compare/v0.3.0...v0.4.0
31
+ [0.3.0]: https://github.com/Invoca/invoca-utils/compare/v0.2.0...v0.3.0
32
+ [0.2.0]: https://github.com/Invoca/invoca-utils/compare/v0.1.1...v0.2.0
data/Gemfile CHANGED
@@ -4,13 +4,19 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
- group :development do
7
+ source 'https://rubygems.org' do
8
+ gem 'activesupport', '~> 4.2'
9
+ gem 'minitest'
10
+ gem 'minitest-reporters'
11
+ gem 'pry'
8
12
  gem 'rake'
9
- gem 'test-unit', '= 1.2.3'
10
13
  gem 'rr', '=1.1.2'
11
- gem 'shoulda', '= 3.5.0'
12
- gem 'pry'
13
14
  gem 'ruby-prof'
14
- gem 'minitest'
15
+ gem 'shoulda', '= 3.5.0'
16
+ gem 'test-unit', '= 1.2.3'
15
17
  gem 'tzinfo'
16
18
  end
19
+
20
+ source 'https://gem.fury.io/invoca' do
21
+ gem 'test_overrides', '~> 0.13'
22
+ end
@@ -1,30 +1,39 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- invoca-utils (0.1.1)
4
+ invoca-utils (0.4.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
+ remote: https://gem.fury.io/invoca/
8
9
  specs:
9
- activesupport (5.1.4)
10
- concurrent-ruby (~> 1.0, >= 1.0.2)
10
+ activesupport (4.2.11.3)
11
11
  i18n (~> 0.7)
12
12
  minitest (~> 5.1)
13
+ thread_safe (~> 0.3, >= 0.3.4)
13
14
  tzinfo (~> 1.1)
14
- coderay (1.1.2)
15
- concurrent-ruby (1.0.5)
16
- hoe (3.16.2)
17
- rake (>= 0.8, < 13.0)
18
- i18n (0.9.1)
15
+ ansi (1.5.0)
16
+ builder (3.2.4)
17
+ coderay (1.1.3)
18
+ concurrent-ruby (1.1.6)
19
+ hoe (3.22.1)
20
+ rake (>= 0.8, < 15.0)
21
+ i18n (0.9.5)
19
22
  concurrent-ruby (~> 1.0)
20
- method_source (0.9.0)
21
- minitest (5.11.1)
22
- pry (0.11.3)
23
- coderay (~> 1.1.0)
24
- method_source (~> 0.9.0)
25
- rake (12.3.0)
23
+ method_source (1.0.0)
24
+ minitest (5.14.1)
25
+ minitest-reporters (1.4.2)
26
+ ansi
27
+ builder
28
+ minitest (>= 5.0)
29
+ ruby-progressbar
30
+ pry (0.13.1)
31
+ coderay (~> 1.1)
32
+ method_source (~> 1.0)
33
+ rake (13.0.1)
26
34
  rr (1.1.2)
27
- ruby-prof (0.17.0)
35
+ ruby-prof (1.4.1)
36
+ ruby-progressbar (1.10.1)
28
37
  shoulda (3.5.0)
29
38
  shoulda-context (~> 1.0, >= 1.0.1)
30
39
  shoulda-matchers (>= 1.4.1, < 3.0)
@@ -33,23 +42,27 @@ GEM
33
42
  activesupport (>= 3.0.0)
34
43
  test-unit (1.2.3)
35
44
  hoe (>= 1.5.1)
45
+ test_overrides (0.13.0)
36
46
  thread_safe (0.3.6)
37
- tzinfo (1.2.4)
47
+ tzinfo (1.2.7)
38
48
  thread_safe (~> 0.1)
39
49
 
40
50
  PLATFORMS
41
51
  ruby
42
52
 
43
53
  DEPENDENCIES
54
+ activesupport (~> 4.2)!
44
55
  invoca-utils!
45
- minitest
46
- pry
47
- rake
48
- rr (= 1.1.2)
49
- ruby-prof
50
- shoulda (= 3.5.0)
51
- test-unit (= 1.2.3)
52
- tzinfo
56
+ minitest!
57
+ minitest-reporters!
58
+ pry!
59
+ rake!
60
+ rr (= 1.1.2)!
61
+ ruby-prof!
62
+ shoulda (= 3.5.0)!
63
+ test-unit (= 1.2.3)!
64
+ test_overrides (~> 0.13)!
65
+ tzinfo!
53
66
 
54
67
  BUNDLED WITH
55
68
  1.17.3
data/Rakefile CHANGED
@@ -3,15 +3,10 @@
3
3
 
4
4
  require "bundler/gem_tasks"
5
5
  require 'rake/testtask'
6
-
7
- Rake::TestTask.new(:test) do |t|
8
- t.libs << 'lib'
9
- t.libs << 'test'
10
- t.pattern = 'test/**/*_test.rb'
11
- t.verbose = false
12
- end
6
+ require 'rake_test_warning_false'
13
7
 
14
8
  task default: :test
15
9
 
16
- task :default => 'test'
17
-
10
+ Rake::TestTask.new do |t|
11
+ t.pattern = "test/**/*_test.rb"
12
+ end
@@ -1,4 +1,4 @@
1
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require 'invoca/utils/version'
4
4
 
@@ -7,8 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.version = Invoca::Utils::VERSION
8
8
  spec.authors = ["Invoca development"]
9
9
  spec.email = ["development@invoca.com"]
10
- spec.summary = %q{A public collection of helpers used in multiple projects}
11
- spec.description = %q{A public collection of helpers used in multiple projects}
10
+ spec.summary = "A public collection of helpers used in multiple projects"
12
11
  spec.homepage = ""
13
12
  spec.license = "MIT"
14
13
 
@@ -1,19 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module Invoca
4
+ module Utils
5
+ end
6
+ end
7
+
8
+ require "invoca/utils/module"
9
+ require "invoca/utils/array"
10
+ require "invoca/utils/enumerable"
3
11
  require "invoca/utils/diff"
12
+ require "invoca/utils/hash"
13
+ require "invoca/utils/hash_with_indifferent_access"
4
14
  require "invoca/utils/http"
5
15
  require "invoca/utils/map_compact"
6
16
  require "invoca/utils/min_max"
7
17
  require "invoca/utils/stable_sort"
8
18
  require "invoca/utils/time"
19
+ require "invoca/utils/exceptions"
9
20
  require "invoca/utils/guaranteed_utf8_string"
10
21
  require "invoca/utils/version"
11
22
 
12
- module Invoca
13
- module Utils
14
- end
15
- end
16
-
17
23
  unless defined?(Diff)
18
24
  Diff = Invoca::Utils::Diff
19
25
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './multi_sender'
4
+
5
+ # Invoca ::Array extensions
6
+ # TODO: Once the hobo_support gem is no longer used by any of our code, use prepend instead of alias
7
+ class Array
8
+
9
+ alias_method :original_multiply_operator, :* # rubocop:disable Style/Alias
10
+
11
+ def *(rhs = nil)
12
+ if rhs
13
+ original_multiply_operator(rhs)
14
+ else
15
+ Invoca::Utils::MultiSender.new(self, :map)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './multi_sender'
4
+
5
+ # Invoca ::Enumerable extensions
6
+ module Enumerable
7
+ def map_and_find(not_found = nil)
8
+ each do |x|
9
+ val = yield(x)
10
+ return val if val
11
+ end
12
+ not_found
13
+ end
14
+
15
+ def map_with_index(res = [])
16
+ each_with_index do |x, i|
17
+ res << yield(x, i)
18
+ end
19
+ res
20
+ end
21
+
22
+ def build_hash(res = {})
23
+ each do |x|
24
+ pair = block_given? ? yield(x) : x
25
+ res[pair.first] = pair.last if pair
26
+ end
27
+ res
28
+ end
29
+
30
+ def map_hash(res = {})
31
+ each do |x|
32
+ v = yield x
33
+ res[x] = v
34
+ end
35
+ res
36
+ end
37
+
38
+ def *
39
+ Invoca::Utils::MultiSender.new(self, :map)
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Utils
5
+ class << self
6
+ # Yields and rescues any exceptions given in `exception_classes`, retrying the given number of times.
7
+ # The final retry does not rescue any exceptions.
8
+ # The try number (0..retries) is yielded as a block param.
9
+ #
10
+ # @param [Class or Array(Class)] exception_classes - exception()s) to rescue
11
+ # @param [Integer] retries: - 1+ count of retries (1 retry = up to 2 tries total)
12
+ # @param [Proc] before_retry - optional proc which is called before each retry, with the exception passed as a block param
13
+ # @return the value from yield
14
+ def retry_on_exception(exception_classes, retries: 1, before_retry: nil)
15
+ retries.times do |attempt_number|
16
+ begin
17
+ return yield(attempt_number)
18
+ rescue *Array(exception_classes) => ex
19
+ before_retry&.call(ex)
20
+ end
21
+ end
22
+
23
+ yield(retries) # no rescue for this last try, so any exceptions will raise out
24
+ end
25
+ end
26
+ end
27
+ end
@@ -21,6 +21,7 @@ module Invoca
21
21
  REPLACE_CHARACTER = '~'
22
22
 
23
23
  class << self
24
+ # normalizes a string to UTF-8
24
25
  def normalize_string(orig_string,
25
26
  normalize_utf16: true,
26
27
  normalize_cp1252: true,
@@ -43,6 +44,22 @@ module Invoca
43
44
  replace_unicode_beyond_ffff: replace_unicode_beyond_ffff)
44
45
  end
45
46
 
47
+ # Walks a JSON doc of hashes, arrays, and values and normalizes all strings found to UTF-8
48
+ def normalize_all_strings(value, **options)
49
+ case value
50
+ when Hash
51
+ value.each_with_object({}) do |(k, v), result|
52
+ result[normalize_all_strings(k, **options)] = normalize_all_strings(v, **options)
53
+ end
54
+ when Array
55
+ value.map { |v| normalize_all_strings(v, **options) }
56
+ when String
57
+ normalize_string(value, **options)
58
+ else
59
+ value
60
+ end
61
+ end
62
+
46
63
  private
47
64
 
48
65
  def normalize_string_from_utf8(string,
@@ -54,13 +71,11 @@ module Invoca
54
71
  found_utf_16 = normalize_utf_16(string, normalize_cp1252: normalize_cp1252) if normalize_utf16
55
72
  if found_utf_16
56
73
  string.encode!('UTF-8')
57
- else
58
- unless string.valid_encoding?
59
- if normalize_cp1252
60
- cp1252_to_utf_8(string)
61
- else
62
- raise ArgumentError, 'Could not normalize to utf8 due to invalid characters (probably CP1252)'
63
- end
74
+ elsif !string.valid_encoding?
75
+ if normalize_cp1252
76
+ cp1252_to_utf_8(string)
77
+ else
78
+ raise ArgumentError, 'Could not normalize to utf8 due to invalid characters (probably CP1252)'
64
79
  end
65
80
  end
66
81
  normalize_newlines(string) if normalize_newlines
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Invoca ::Hash extensions
4
+ class Hash
5
+ def select_hash(&block)
6
+ res = {}
7
+ each { |k, v| res[k] = v if (block.arity == 1 ? yield(v) : yield(k, v)) } # rubocop:disable Style/ParenthesesAroundCondition
8
+ res
9
+ end
10
+
11
+ def map_hash(&block)
12
+ res = {}
13
+ each { |k, v| res[k] = block.arity == 1 ? yield(v) : yield(k, v) }
14
+ res
15
+ end
16
+
17
+ def partition_hash(keys = nil)
18
+ yes = {}
19
+ no = {}
20
+ each do |k, v|
21
+ if block_given? ? yield(k, v) : keys.include?(k)
22
+ yes[k] = v
23
+ else
24
+ no[k] = v
25
+ end
26
+ end
27
+ [yes, no]
28
+ end
29
+
30
+ # rubocop:disable Naming/BinaryOperatorParameterName
31
+ def -(keys)
32
+ res = {}
33
+ each_pair { |k, v| res[k] = v unless k.in?(keys) }
34
+ res
35
+ end
36
+
37
+ def &(keys)
38
+ res = {}
39
+ keys.each { |k| res[k] = self[k] if has_key?(k) }
40
+ res
41
+ end
42
+ # rubocop:enable Naming/BinaryOperatorParameterName
43
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Invoca ::HashWithIndifferentAccess extensions
4
+ if defined? HashWithIndifferentAccess
5
+
6
+ class HashWithIndifferentAccess
7
+
8
+ # rubocop:disable Naming/BinaryOperatorParameterName
9
+ def -(keys)
10
+ res = HashWithIndifferentAccess.new
11
+ keys = keys.map { |k| k.is_a?(Symbol) ? k.to_s : k }
12
+ each_pair { |k, v| res[k] = v unless k.in?(keys) }
13
+ res
14
+ end
15
+
16
+ def &(keys)
17
+ res = HashWithIndifferentAccess.new
18
+ keys.each do |k|
19
+ k = k.to_s if k.is_a?(Symbol)
20
+ res[k] = self[k] if has_key?(k)
21
+ end
22
+ res
23
+ end
24
+ # rubocop:enable Naming/BinaryOperatorParameterName
25
+
26
+ def partition_hash(keys = nil)
27
+ keys = keys&.map { |k| k.is_a?(Symbol) ? k.to_s : k }
28
+ yes = HashWithIndifferentAccess.new
29
+ no = HashWithIndifferentAccess.new
30
+ each do |k, v|
31
+ if block_given? ? yield(k, v) : keys.include?(k)
32
+ yes[k] = v
33
+ else
34
+ no[k] = v
35
+ end
36
+ end
37
+ [yes, no]
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Module
4
+
5
+ # Custom alias_method_chain that won't cause infinite recursion if called twice.
6
+ # NOTE: Calling alias_method_chain on alias_method_chain was just way too confusing, so I copied it :-/
7
+ def alias_method_chain(target, feature)
8
+ # Strip out punctuation on predicates, bang or writer methods since
9
+ # e.g. target?_without_feature is not a valid method name.
10
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 # rubocop:disable Style/PerlBackrefs
11
+ yield(aliased_target, punctuation) if block_given?
12
+
13
+ with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
14
+ without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
15
+
16
+ unless method_defined?(without_method)
17
+ alias_method without_method, target
18
+ alias_method target, with_method
19
+
20
+ if public_method_defined?(without_method)
21
+ public target
22
+ elsif protected_method_defined?(without_method)
23
+ protected target
24
+ elsif private_method_defined?(without_method)
25
+ private target
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Utils
5
+ class MultiSender
6
+ undef_method(*(instance_methods - [:__id__, :__send__, :object_id]))
7
+
8
+ def initialize(enumerable, method)
9
+ @enumerable = enumerable
10
+ @method = method
11
+ end
12
+
13
+ # rubocop:disable Style/MethodMissingSuper
14
+ # rubocop:disable Style/MissingRespondToMissing
15
+ def method_missing(name, *args, &block)
16
+ @enumerable.send(@method) { |x| x.send(name, *args, &block) }
17
+ end
18
+ # rubocop:enable Style/MethodMissingSuper
19
+ # rubocop:enable Style/MissingRespondToMissing
20
+ end
21
+ end
22
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Invoca
4
4
  module Utils
5
- VERSION = "0.1.1"
5
+ VERSION = "0.4.1"
6
6
  end
7
7
  end
@@ -7,3 +7,8 @@ require 'pry'
7
7
  require 'active_support/all'
8
8
 
9
9
  require 'invoca/utils'
10
+ require "minitest/reporters"
11
+ Minitest::Reporters.use! [
12
+ Minitest::Reporters::DefaultReporter.new,
13
+ Minitest::Reporters::JUnitReporter.new
14
+ ]
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/invoca/utils/array.rb'
4
+ require_relative '../test_helper'
5
+
6
+ class ArrayTest < Minitest::Test
7
+ context '* operator' do
8
+ should 'call the same method on each item in an array and return the results as an array' do
9
+ assert_equal([4, 5, 5], ['some', 'short', 'words'].*.length)
10
+ end
11
+
12
+ should 'handle methods with arguments' do
13
+ assert_equal(['om', 'ho', 'or'], ['some', 'short', 'words'].*.slice(1, 2))
14
+ end
15
+
16
+ should 'not alter normal behavior (multiplication) when there is a right hand side to the expression' do
17
+ assert_equal(['some', 'short', 'words', 'some', 'short', 'words'], ['some', 'short', 'words'] * 2)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative '../../lib/invoca/utils/enumerable.rb'
5
+ require_relative '../test_helper'
6
+
7
+ class EnumerableTest < Minitest::Test
8
+
9
+ context 'map_and_find' do
10
+ should 'return the mapped value of the first match' do
11
+ assert_equal('FOUND 3', [1, 2, 3, 4].map_and_find { |v| 'FOUND 3' if v == 3 })
12
+ end
13
+
14
+ should 'return the mapped value of the first match, even if there are multiple matches' do
15
+ assert_equal('FOUND 3', [1, 2, 3, 4].map_and_find { |v| "FOUND #{v}" if v > 2 })
16
+ end
17
+
18
+ should 'return the provided argument if the value is not found' do
19
+ assert_equal('NOT FOUND', [1, 2, 3, 4].map_and_find('NOT FOUND') { |v| "FOUND 6" if v == 6 })
20
+ end
21
+
22
+ should 'return nil if the value is not found and no argument is provided' do
23
+ assert_nil([1, 2, 3, 4].map_and_find { |v| "FOUND 6" if v == 6 })
24
+ end
25
+ end
26
+
27
+ context 'map_with_index' do
28
+ should 'call the block with the value and index' do
29
+ assert_equal([10, 21, 32, 43], [10, 20, 30, 40].map_with_index { |v, index| v + index })
30
+ end
31
+
32
+ should 'assumulate into the provided enumerable' do
33
+ assert_equal([1, 10, 21, 32, 43], [10, 20, 30, 40].map_with_index([1]) { |v, index| v + index })
34
+ end
35
+ end
36
+
37
+ context 'map_hash' do
38
+ should 'convert enumerables into a hash using the value for key and the map result as the hash value' do
39
+ assert_equal({ 1 => 11, 2 => 12, 3 => 13 }, [1, 2, 3].map_hash { |v| v + 10 })
40
+ end
41
+
42
+ should 'includes nils returned from map' do
43
+ assert_equal({ 1 => 11, 2 => nil, 3 => 13 }, [1, 2, 3].map_hash { |v| v + 10 unless v == 2 })
44
+ end
45
+ end
46
+
47
+ context 'build_hash' do
48
+ should 'convert arrays of [key, value] to a hash of { key => value }' do
49
+ assert_equal({ 'some' => 4, 'short' => 5, 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| [s, s.length] })
50
+ end
51
+
52
+ should 'ignore nils returned from map' do
53
+ assert_equal({ 'some' => 4, 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| s == 'short' ? nil : [s, s.length] })
54
+ end
55
+
56
+ # these seem like erroneous behavior, but, they have been left as-is for backward compatibility with hobosupport::Enumerable::build_hash
57
+
58
+ should 'convert arrays of [single_value] to a hash of { single_value => single_value }' do
59
+ assert_equal({ 'some' => 4, 'short' => 'short', 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| s == 'short' ? [s] : [s, s.length] })
60
+ end
61
+
62
+ should 'convert arrays of [first, ..., last] to a hash of { first => last }' do
63
+ assert_equal({ 'some' => 4, 'short' => 'three', 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| s == 'short' ? [s, 'two', 'three'] : [s, s.length] })
64
+ end
65
+
66
+ should 'convert empty arrays to a hash of { nil => nil }' do
67
+ assert_equal({ 'some' => 4, nil => nil, 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| s == 'short' ? [] : [s, s.length] })
68
+ end
69
+ end
70
+
71
+ context '* operator' do
72
+ should 'call the same method on each item in an Set and return the results as an array' do
73
+ assert_equal([4, 5, 5], Set['some', 'short', 'words'].*.length)
74
+ end
75
+
76
+ should 'call the same method on each item in an Hash and return the results as an array' do
77
+ assert_equal(['key1:value1', 'key2:value2'], { key1: 'value1', key2: 'value2' }.*.join(':'))
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class ExceptionsTest < Minitest::Test
6
+ context "exceptions" do
7
+ context ".retry_on_exception" do
8
+ should "default retries: to 1" do
9
+ times = 0
10
+ tries = []
11
+ result = Invoca::Utils.retry_on_exception(ArgumentError) do |try|
12
+ tries << try
13
+ times += 1
14
+ end
15
+ assert_equal 1, result
16
+ assert_equal [0], tries
17
+ end
18
+
19
+ context "when never raising an exception" do
20
+ should "return result" do
21
+ times = 0
22
+ tries = []
23
+ result = Invoca::Utils.retry_on_exception(ArgumentError, retries: 2) do |try|
24
+ tries << try
25
+ times += 1
26
+ try == 0 and raise ArgumentError, '!!!'
27
+ times
28
+ end
29
+ assert_equal 2, result
30
+ assert_equal [0, 1], tries
31
+ end
32
+ end
33
+
34
+ context "when always raising an exception" do
35
+ should "retry and finally raise" do
36
+ tries = []
37
+ assert_raises(ArgumentError, /!!! 2/) do
38
+ Invoca::Utils.retry_on_exception(ArgumentError, retries: 1) do |try|
39
+ tries << try
40
+ raise ArgumentError, "!!! #{try + 1}"
41
+ end
42
+ end
43
+ assert_equal [0, 1], tries
44
+ end
45
+ end
46
+
47
+ context "when raising but then succeeding" do
48
+ should "retry and finally return result" do
49
+ times = 0
50
+ result = Invoca::Utils.retry_on_exception(ArgumentError, retries: 1) do
51
+ times += 1
52
+ if times == 1
53
+ raise ArgumentError, "!!! #{times}"
54
+ else
55
+ times
56
+ end
57
+ end
58
+ assert_equal 2, result
59
+ end
60
+ end
61
+
62
+ context "when raising different exceptions (array notation) but then succeeding" do
63
+ should "retry and finally return result" do
64
+ times = 0
65
+ tries = []
66
+ result = Invoca::Utils.retry_on_exception([ArgumentError, RuntimeError], retries: 2) do |try|
67
+ tries << try
68
+ times += 1
69
+ case times
70
+ when 1
71
+ raise ArgumentError, "!!! #{times}"
72
+ when 2
73
+ raise RuntimeError, "!!! #{times}"
74
+ else
75
+ times
76
+ end
77
+ end
78
+ assert_equal 3, result
79
+ assert_equal [0, 1, 2], tries
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -221,6 +221,29 @@ class GuaranteedUTF8StringTest < Minitest::Test
221
221
  assert_equal Encoding::UTF_8, encoded_string.encoding
222
222
  end
223
223
  end
224
+
225
+ context ".normalize_strings" do
226
+ should "walk json doc, replacing strings in: values, inside array elements, and hash keys and values" do
227
+ json_doc = {
228
+ '😹' => "\xE2\x9C\x93 laughing cat",
229
+ ['😹'] => ["\xE2", "\xf0\x9f\x98\xb9", { "newline" => "\r\n" }],
230
+ 'cp1252' => "\x91smart quotes\x92"
231
+ }
232
+
233
+ normalized_json = Invoca::Utils::GuaranteedUTF8String.normalize_all_strings(json_doc,
234
+ normalize_utf16: true,
235
+ normalize_cp1252: true,
236
+ normalize_newlines: true,
237
+ remove_utf8_bom: true,
238
+ replace_unicode_beyond_ffff: true)
239
+
240
+ assert_equal({
241
+ '~' => "✓ laughing cat",
242
+ ['~'] => ["â", "~", { "newline" => "\n" }],
243
+ 'cp1252' => "‘smart quotes’"
244
+ }, normalized_json)
245
+ end
246
+ end
224
247
  end
225
248
 
226
249
  context 'constructor' do
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/invoca/utils/hash.rb'
4
+ require_relative '../test_helper'
5
+
6
+ class HashTest < Minitest::Test
7
+
8
+ context 'select_hash' do
9
+ should 'return a hash containing key/values identified by the block' do
10
+ assert_equal({ 1 => 2, 3 => 4 }, { 1 => 2, 3 => 4, 6 => 5 }.select_hash { |key, value| key < value })
11
+ end
12
+
13
+ should 'handle blocks that only check values' do
14
+ assert_equal({ 3 => 4, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 }.select_hash { |value| value != 2 })
15
+ end
16
+ end
17
+
18
+ context 'map_hash' do
19
+ should 'return a hash containing values updated by the block' do
20
+ assert_equal({ 1 => true, 3 => true, 6 => false }, { 1 => 2, 3 => 4, 6 => 5 }.map_hash { |key, value| key < value })
21
+ end
22
+
23
+ should 'handle blocks that only receive values' do
24
+ assert_equal({ 1 => 4, 3 => 8, 6 => 10 }, { 1 => 2, 3 => 4, 6 => 5 }.map_hash { |value| value * 2 })
25
+ end
26
+ end
27
+
28
+ context 'partition_hash' do
29
+ should 'return two hashes, the first contains the pairs with matching keys, the second contains the rest' do
30
+ assert_equal([{ 1 => 2, 3 => 4 }, { 6 => 5 }], { 1 => 2, 3 => 4, 6 => 5 }.partition_hash([1, 3]))
31
+ end
32
+
33
+ should 'return two hashes, the first contains the pairs with identified by the block, the second contains the rest' do
34
+ assert_equal([{ 1 => 2, 3 => 4 }, { 6 => 5 }], { 1 => 2, 3 => 4, 6 => 5 }.partition_hash { |key, value| key < value })
35
+ end
36
+
37
+ should 'handle no matches' do
38
+ assert_equal([{}, { 1 => 2, 3 => 4, 6 => 5 }], { 1 => 2, 3 => 4, 6 => 5 }.partition_hash([100]))
39
+ end
40
+
41
+ should 'handle all matches' do
42
+ assert_equal([{ 1 => 2, 3 => 4, 6 => 5 }, {}], { 1 => 2, 3 => 4, 6 => 5 }.partition_hash { |_key, _value| true })
43
+ end
44
+ end
45
+
46
+ context '- operator' do
47
+ should 'return a hash with pairs removed that match the keys in rhs array' do
48
+ assert_equal({ 3 => 4 }, { 1 => 2, 3 => 4, 6 => 5 } - [1, 6])
49
+ end
50
+
51
+ should 'handle empty rhs array' do
52
+ assert_equal({ 1 => 2, 3 => 4, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 } - [])
53
+ end
54
+
55
+ should 'handle no matches in rhs array' do
56
+ assert_equal({ 1 => 2, 3 => 4, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 } - [100, 600])
57
+ end
58
+
59
+ should 'handle all matches in rhs array' do
60
+ assert_equal({}, { 1 => 2, 3 => 4, 6 => 5 } - [1, 3, 6])
61
+ end
62
+ end
63
+
64
+ context '& operator' do
65
+ should 'return a hash with pairs removed that do NOT match the keys in rhs array' do
66
+ assert_equal({ 1 => 2, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 } & [1, 6])
67
+ end
68
+
69
+ should 'handle empty rhs array' do
70
+ assert_equal({}, { 1 => 2, 3 => 4, 6 => 5 } & [])
71
+ end
72
+
73
+ should 'handle no matches in rhs array' do
74
+ assert_equal({}, { 1 => 2, 3 => 4, 6 => 5 } & [100, 600])
75
+ end
76
+
77
+ should 'handle all matches in rhs array' do
78
+ assert_equal({ 1 => 2, 3 => 4, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 } & [1, 3, 6])
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/hash_with_indifferent_access'
4
+ require_relative '../../lib/invoca/utils/hash_with_indifferent_access.rb'
5
+ require_relative '../test_helper'
6
+
7
+ class HashWithIndifferentAccessTest < Minitest::Test
8
+
9
+ context 'partition_hash' do
10
+ setup do
11
+ @hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
12
+ end
13
+
14
+ should 'return two hashes, the first contains the pairs with matching keys, the second contains the rest' do
15
+ assert_equal([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }], @hash_to_test.partition_hash(['one', 'three']))
16
+ end
17
+
18
+ should 'return two hashes, the first contains the pairs with identified by the block, the second contains the rest' do
19
+ assert_equal([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }], @hash_to_test.partition_hash { |key, _value| ['one', 'three'].include?(key) })
20
+ end
21
+
22
+ should 'handle no matches' do
23
+ assert_equal([{}, @hash_to_test], @hash_to_test.partition_hash([:not_found]))
24
+ end
25
+
26
+ should 'handle all matches' do
27
+ assert_equal([@hash_to_test, {}], @hash_to_test.partition_hash { |_key, _value| true })
28
+ end
29
+
30
+ should 'handle symbols for key matching' do
31
+ assert_equal([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }], @hash_to_test.partition_hash([:one, :three]))
32
+ end
33
+
34
+ should 'return HashWithIndifferentAccess objects' do
35
+ matched, unmatched = @hash_to_test.partition_hash([:one, :three])
36
+ assert(matched.is_a?(HashWithIndifferentAccess))
37
+ assert(unmatched.is_a?(HashWithIndifferentAccess))
38
+ end
39
+ end
40
+
41
+ context '- operator' do
42
+ setup do
43
+ @hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
44
+ end
45
+
46
+ should 'return a hash with pairs removed that match the keys in rhs array' do
47
+ assert_equal({ 'three' => 4 }, @hash_to_test - ['one', 'six'])
48
+ end
49
+
50
+ should 'handle empty rhs array' do
51
+ assert_equal(@hash_to_test, @hash_to_test - [])
52
+ end
53
+
54
+ should 'handle no matches in rhs array' do
55
+ assert_equal(@hash_to_test, @hash_to_test - ['100', '600'])
56
+ end
57
+
58
+ should 'handle all matches in rhs array' do
59
+ assert_equal({}, @hash_to_test - ['one', 'three', 'six'])
60
+ end
61
+
62
+ should 'handle symbols for key matching' do
63
+ assert_equal({ 'six' => 5 }, @hash_to_test - [:one, :three])
64
+ end
65
+
66
+ should 'return HashWithIndifferentAccess object' do
67
+ assert((@hash_to_test - [:one, :three]).is_a?(HashWithIndifferentAccess))
68
+ end
69
+ end
70
+
71
+ context '& operator' do
72
+ setup do
73
+ @hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
74
+ end
75
+
76
+ should 'return a hash with pairs removed that do NOT match the keys in rhs array' do
77
+ assert_equal({ 'one' => 2, 'six' => 5 }, @hash_to_test & ['one', 'six'])
78
+ end
79
+
80
+ should 'handle empty rhs array' do
81
+ assert_equal({}, @hash_to_test & [])
82
+ end
83
+
84
+ should 'handle no matches in rhs array' do
85
+ assert_equal({}, @hash_to_test & ['100', '600'])
86
+ end
87
+
88
+ should 'handle all matches in rhs array' do
89
+ assert_equal(@hash_to_test, @hash_to_test & ['one', 'three', 'six'])
90
+ end
91
+
92
+ should 'handle symbols for key matching' do
93
+ assert_equal({ 'one' => 2, 'three' => 4 }, @hash_to_test & [:one, :three])
94
+ end
95
+
96
+ should 'return HashWithIndifferentAccess object' do
97
+ assert((@hash_to_test & [:one, :three]).is_a?(HashWithIndifferentAccess))
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # must require active_support's alias_method_chain first, to ensure that our module monkey patches it
4
+ require 'active_support/core_ext/module/aliasing'
5
+ require_relative '../../lib/invoca/utils/module.rb'
6
+ require_relative '../test_helper'
7
+
8
+ class ModuleTest < Minitest::Test
9
+ class NumberFun
10
+ def self.around_filter(around_method, method_names)
11
+ method_names.each do |meth|
12
+ define_method("#{meth}_with_around_filter") do |*args|
13
+ send(around_method, *args) do |*ar_args|
14
+ send("#{meth}_without_around_filter", *ar_args)
15
+ end
16
+ end
17
+
18
+ alias_method_chain meth, :around_filter
19
+ end
20
+ end
21
+
22
+ def increment_filter(num)
23
+ yield(num + 1)
24
+ end
25
+
26
+ def number_printer(num)
27
+ num
28
+ end
29
+
30
+ around_filter :increment_filter, [:number_printer]
31
+ around_filter :increment_filter, [:number_printer]
32
+ end
33
+
34
+ context 'alias_method_chain' do
35
+ should 'not cause infinite recursion when double aliasing the same method' do
36
+ assert_equal(4, NumberFun.new.number_printer(3))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/invoca/utils/multi_sender.rb'
4
+ require_relative '../test_helper'
5
+
6
+ class MultiSenderTest < Minitest::Test
7
+ # create enumerable class for testing
8
+ class LinkedList
9
+ include Enumerable
10
+
11
+ def initialize(head, tail = nil)
12
+ @head, @tail = head, tail
13
+ end
14
+
15
+ def <<(item)
16
+ LinkedList.new(item, self)
17
+ end
18
+
19
+ def inspect
20
+ [@head, @tail].inspect
21
+ end
22
+
23
+ def each(&block)
24
+ if block_given?
25
+ block.call(@head)
26
+ @tail&.each(&block)
27
+ else
28
+ to_enum(:each)
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'MultiSender' do
34
+ context 'with custom Enumerable' do
35
+ setup do
36
+ linked_list = LinkedList.new('some') << 'short' << 'words'
37
+ @multi_sender = Invoca::Utils::MultiSender.new(linked_list, :map)
38
+ end
39
+
40
+ should 'call the same method on each item in an Enumerable and return the results as an array' do
41
+ assert_equal([5, 5, 4], @multi_sender.length)
42
+ end
43
+
44
+ should 'handle methods with arguments' do
45
+ assert_equal(['or', 'ho', 'om'], @multi_sender.slice(1, 2))
46
+ end
47
+ end
48
+
49
+ context 'with built-in Array' do
50
+ should 'call the same method on each item in an Array and return the results as an array' do
51
+ multi_sender = Invoca::Utils::MultiSender.new(['some', 'short', 'words'], :map)
52
+ assert_equal([4, 5, 5], multi_sender.length)
53
+ end
54
+ end
55
+ end
56
+ end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invoca-utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca development
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-16 00:00:00.000000000 Z
11
+ date: 2020-06-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: A public collection of helpers used in multiple projects
13
+ description:
14
14
  email:
15
15
  - development@invoca.com
16
16
  executables: []
@@ -18,8 +18,11 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - ".gitignore"
21
+ - ".jenkins/Jenkinsfile"
22
+ - ".jenkins/ruby_build_pod.yml"
21
23
  - ".rubocop.yml"
22
24
  - ".ruby-version"
25
+ - CHANGELOG.md
23
26
  - Gemfile
24
27
  - Gemfile.lock
25
28
  - LICENSE.txt
@@ -27,18 +30,32 @@ files:
27
30
  - Rakefile
28
31
  - invoca-utils.gemspec
29
32
  - lib/invoca/utils.rb
33
+ - lib/invoca/utils/array.rb
30
34
  - lib/invoca/utils/diff.rb
35
+ - lib/invoca/utils/enumerable.rb
36
+ - lib/invoca/utils/exceptions.rb
31
37
  - lib/invoca/utils/guaranteed_utf8_string.rb
38
+ - lib/invoca/utils/hash.rb
39
+ - lib/invoca/utils/hash_with_indifferent_access.rb
32
40
  - lib/invoca/utils/http.rb
33
41
  - lib/invoca/utils/map_compact.rb
34
42
  - lib/invoca/utils/min_max.rb
43
+ - lib/invoca/utils/module.rb
44
+ - lib/invoca/utils/multi_sender.rb
35
45
  - lib/invoca/utils/stable_sort.rb
36
46
  - lib/invoca/utils/time.rb
37
47
  - lib/invoca/utils/version.rb
38
48
  - test/helpers/constant_overrides.rb
39
49
  - test/test_helper.rb
50
+ - test/unit/array_test.rb
51
+ - test/unit/enumerable_test.rb
52
+ - test/unit/exceptions_test.rb
40
53
  - test/unit/guaranteed_utf8_string_test.rb
54
+ - test/unit/hash_test.rb
55
+ - test/unit/hash_with_indifferent_access_test.rb
41
56
  - test/unit/map_compact_test.rb
57
+ - test/unit/module_test.rb
58
+ - test/unit/multi_sender_test.rb
42
59
  - test/unit/stable_sort_test.rb
43
60
  - test/unit/time_calculations_test.rb
44
61
  - test/unit/utils_test.rb
@@ -62,16 +79,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
79
  - !ruby/object:Gem::Version
63
80
  version: '0'
64
81
  requirements: []
65
- rubyforge_project:
66
- rubygems_version: 2.6.13
82
+ rubygems_version: 3.0.3
67
83
  signing_key:
68
84
  specification_version: 4
69
85
  summary: A public collection of helpers used in multiple projects
70
86
  test_files:
71
87
  - test/helpers/constant_overrides.rb
72
88
  - test/test_helper.rb
89
+ - test/unit/array_test.rb
90
+ - test/unit/enumerable_test.rb
91
+ - test/unit/exceptions_test.rb
73
92
  - test/unit/guaranteed_utf8_string_test.rb
93
+ - test/unit/hash_test.rb
94
+ - test/unit/hash_with_indifferent_access_test.rb
74
95
  - test/unit/map_compact_test.rb
96
+ - test/unit/module_test.rb
97
+ - test/unit/multi_sender_test.rb
75
98
  - test/unit/stable_sort_test.rb
76
99
  - test/unit/time_calculations_test.rb
77
100
  - test/unit/utils_test.rb