umbrellio-utils 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.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +60 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +19 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +132 -0
- data/Rakefile +8 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/lib/umbrellio-utils.rb +3 -0
- data/lib/umbrellio_utils.rb +47 -0
- data/lib/umbrellio_utils/cards.rb +27 -0
- data/lib/umbrellio_utils/checks.rb +69 -0
- data/lib/umbrellio_utils/constants.rb +28 -0
- data/lib/umbrellio_utils/control.rb +57 -0
- data/lib/umbrellio_utils/database.rb +83 -0
- data/lib/umbrellio_utils/formatting.rb +64 -0
- data/lib/umbrellio_utils/http_client.rb +39 -0
- data/lib/umbrellio_utils/misc.rb +28 -0
- data/lib/umbrellio_utils/parsing.rb +76 -0
- data/lib/umbrellio_utils/passwords.rb +15 -0
- data/lib/umbrellio_utils/random.rb +11 -0
- data/lib/umbrellio_utils/request_wrapper.rb +66 -0
- data/lib/umbrellio_utils/rounding.rb +37 -0
- data/lib/umbrellio_utils/store.rb +43 -0
- data/lib/umbrellio_utils/vault.rb +29 -0
- data/lib/umbrellio_utils/version.rb +5 -0
- data/umbrellio_utils.gemspec +40 -0
- metadata +214 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7a8a024f0f4fc2df65b3ec0a9e8bfafc71122522cc9e633dec609ed7f03f1a59
|
4
|
+
data.tar.gz: 5e66a79927988a0a2232308315d8e0e07ccb3fcb40c9c770963babd81f77e47e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d8a5425810747fb97922019b388e54358623b39d119ea0482592d39f3ffda8efa54768b20d896ebcac3b2877059da24212ff2811c255eb29506108946309d8c4
|
7
|
+
data.tar.gz: e918d6b8324560d613fef02b68625373b6285b8a9791bbf5072c1a1bb0117b73f9699edc508f2456d88b7a3307cbc3ba42a86103f554518685b539efb3fd1728
|
@@ -0,0 +1,60 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
env:
|
6
|
+
FULL_COVERAGE_CHECK: false # Sometimes, sometimes...
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
full-check:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
|
12
|
+
# We want to run on external PRs, but not on our own internal PRs as they'll be run on push event
|
13
|
+
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'umbrellio/utils'
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: 3
|
20
|
+
bundler-cache: true
|
21
|
+
- name: Run Linter
|
22
|
+
run: bundle exec ci-helper RubocopLint
|
23
|
+
- name: Check missed spec suffixes
|
24
|
+
run: bundle exec ci-helper CheckSpecSuffixes --extra-paths spec/*.rb --ignored-paths spec/*_helper.rb
|
25
|
+
- name: Run specs
|
26
|
+
run: bundle exec ci-helper RunSpecs
|
27
|
+
- name: Audit
|
28
|
+
run: bundle exec ci-helper BundlerAudit
|
29
|
+
- name: Coveralls
|
30
|
+
uses: coverallsapp/github-action@master
|
31
|
+
with:
|
32
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
33
|
+
specs:
|
34
|
+
runs-on: ubuntu-latest
|
35
|
+
continue-on-error: ${{ matrix.experimental }}
|
36
|
+
|
37
|
+
env:
|
38
|
+
FULL_TEST_COVERAGE_CHECK: false
|
39
|
+
|
40
|
+
# We want to run on external PRs, but not on our own internal PRs as they'll be run on push event
|
41
|
+
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'umbrellio/utils'
|
42
|
+
|
43
|
+
strategy:
|
44
|
+
fail-fast: false
|
45
|
+
matrix:
|
46
|
+
ruby: [2.6, 2.7]
|
47
|
+
experimental: [false]
|
48
|
+
include:
|
49
|
+
- ruby: head
|
50
|
+
experimental: true
|
51
|
+
|
52
|
+
|
53
|
+
steps:
|
54
|
+
- uses: actions/checkout@v2
|
55
|
+
- uses: ruby/setup-ruby@v1
|
56
|
+
with:
|
57
|
+
ruby-version: ${{ matrix.ruby }}
|
58
|
+
bundler-cache: true
|
59
|
+
- name: Run specs
|
60
|
+
run: bundle exec rspec
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
inherit_gem:
|
2
|
+
rubocop-config-umbrellio: lib/rubocop.yml
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
DisplayCopNames: true
|
6
|
+
TargetRubyVersion: 2.6
|
7
|
+
|
8
|
+
Naming/MethodParameterName:
|
9
|
+
AllowedNames: ["x", "y", "z"]
|
10
|
+
|
11
|
+
RSpec/EmptyLineAfterHook:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Naming/FileName:
|
15
|
+
Exclude:
|
16
|
+
- lib/umbrellio-utils.rb
|
17
|
+
|
18
|
+
Naming/RescuedExceptionsVariableName:
|
19
|
+
Enabled: false
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 JustAnotherDude
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
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 FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# Umbrellio Utils
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem "umbrellio-utils"
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
$ bundle install
|
15
|
+
```
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
$ gem install umbrellio-utils
|
21
|
+
```
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Quick Start
|
26
|
+
|
27
|
+
You can use modules and classes directly by accessing modules and classes
|
28
|
+
under namespace `UmbrellioUtils`. Or you can `include UmbrellioUtils` to other
|
29
|
+
module with name you like.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# Direct using
|
33
|
+
UmbrellioUtils::Constants.get_class!(:object) #=> Object
|
34
|
+
|
35
|
+
# Aliasing to shorter name.
|
36
|
+
|
37
|
+
module Utils
|
38
|
+
include UmbrellioUtils
|
39
|
+
end
|
40
|
+
|
41
|
+
Utils::Constants.get_class!(:object) #=> Object
|
42
|
+
Utils::Constants #=> UmbrellioUtils::Constants
|
43
|
+
```
|
44
|
+
|
45
|
+
### Configuration
|
46
|
+
|
47
|
+
Some modules and classes are configurable. Here's the full list of settings and what they do:
|
48
|
+
|
49
|
+
- `store_table_name` — table which is used by `UmbrellioUtils::Store` module.
|
50
|
+
Defaults to `:store`
|
51
|
+
- `http_client_name` — fiber-local variable name for http client instance in
|
52
|
+
`UmbrellioUtils::HTTPClient`. Defaults to `:application_httpclient`
|
53
|
+
|
54
|
+
You can change config in two ways. Firstly, you can change values by accessing configuration
|
55
|
+
directly. Secondly, you can use `UmbrellioUtils::configure` method, which accepts a block.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
|
59
|
+
# First method
|
60
|
+
|
61
|
+
UmbrellioUtils.config.store_table_name = :cool_name
|
62
|
+
|
63
|
+
# Second method
|
64
|
+
|
65
|
+
module Utils
|
66
|
+
include UmbrellioUtils
|
67
|
+
|
68
|
+
configure do |config|
|
69
|
+
config.store_table_name = :cool_name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
Keep in mind that the config is common to all modules: if you use multiple modules that include
|
75
|
+
`UmbrellioUtils`, then all modules will use the same configuration object.
|
76
|
+
|
77
|
+
### Extension
|
78
|
+
|
79
|
+
You can extend module with you own project specific methods
|
80
|
+
via `UmbrellioUtils::extend_util!`.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
module Utils
|
84
|
+
include UmbrellioUtils
|
85
|
+
|
86
|
+
configure do |config|
|
87
|
+
config.store_table_name = :cool_name
|
88
|
+
end
|
89
|
+
|
90
|
+
extend_util!(:Constants) do
|
91
|
+
def useful_method
|
92
|
+
"Just string"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Utils::Constants.useful_method #=> "Just string"
|
98
|
+
```
|
99
|
+
|
100
|
+
Or you can define methods in your module and then extend the desired module.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
module MyHelpers
|
104
|
+
def useful_method
|
105
|
+
"Just string"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
module Utils
|
110
|
+
include UmbrellioUtils
|
111
|
+
|
112
|
+
extend_util!(:Constants) { extend MyHelpers }
|
113
|
+
end
|
114
|
+
|
115
|
+
Utils::Constants.useful_method #=> "Just string"
|
116
|
+
```
|
117
|
+
|
118
|
+
## Contributing
|
119
|
+
|
120
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/umbrellio/utils.
|
121
|
+
|
122
|
+
## License
|
123
|
+
|
124
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
125
|
+
|
126
|
+
## Authors
|
127
|
+
|
128
|
+
Created by Umbrellio's Ruby developers
|
129
|
+
|
130
|
+
<a href="https://github.com/umbrellio/">
|
131
|
+
<img style="float: left;" src="https://umbrellio.github.io/Umbrellio/supported_by_umbrellio.svg" alt="Supported by Umbrellio" width="439" height="72">
|
132
|
+
</a>
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "memery"
|
4
|
+
|
5
|
+
module UmbrellioUtils
|
6
|
+
CONFIG_SET_MUTEX = Mutex.new
|
7
|
+
CONFIG_MUTEX = Mutex.new
|
8
|
+
EXTENSION_MUTEX = Mutex.new
|
9
|
+
|
10
|
+
Dir["#{__dir__}/*/*.rb"].each { |file_path| require_relative(file_path) }
|
11
|
+
|
12
|
+
extend self
|
13
|
+
|
14
|
+
def included(othermod)
|
15
|
+
super
|
16
|
+
othermod.extend(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
# rubocop:disable Style/ClassVars
|
20
|
+
def config
|
21
|
+
CONFIG_SET_MUTEX.synchronize do
|
22
|
+
@@config ||= Struct
|
23
|
+
.new(:store_table_name, :http_client_name, keyword_init: true)
|
24
|
+
.new(**default_settings)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# rubocop:enable Style/ClassVars
|
29
|
+
|
30
|
+
def configure
|
31
|
+
CONFIG_MUTEX.synchronize { yield config }
|
32
|
+
end
|
33
|
+
|
34
|
+
def extend_util!(module_name, &block)
|
35
|
+
const = UmbrellioUtils.const_get(module_name)
|
36
|
+
EXTENSION_MUTEX.synchronize { const.class_eval(&block) }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def default_settings
|
42
|
+
{
|
43
|
+
store_table_name: :store,
|
44
|
+
http_client_name: :application_httpclient,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Cards
|
5
|
+
extend self
|
6
|
+
|
7
|
+
InvalidExpiryDateString = Class.new(StandardError)
|
8
|
+
|
9
|
+
def parse_expiry_date!(string, **options)
|
10
|
+
result = parse_expiry_date(string, **options)
|
11
|
+
|
12
|
+
unless result
|
13
|
+
raise InvalidExpiryDateString, "Failed to parse expiry date: #{string.inspect}"
|
14
|
+
end
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_expiry_date(string)
|
20
|
+
month, year = string.split("/", 2).map(&:to_i)
|
21
|
+
return unless month && year
|
22
|
+
year += 2000 if year < 100
|
23
|
+
time = suppress(ArgumentError) { Time.zone.local(year, month) }
|
24
|
+
time + 1.month - 1.second if time
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Checks
|
5
|
+
extend self
|
6
|
+
|
7
|
+
VOWELS_REGEX = /[AEIOUY]/.freeze
|
8
|
+
CONSONANTS_REGEX = /[BCDFGHJKLMNPQRSTVXZW]/.freeze
|
9
|
+
EMAIL_REGEXP = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i.freeze
|
10
|
+
|
11
|
+
def secure_compare(src, dest)
|
12
|
+
ActiveSupport::SecurityUtils.secure_compare(
|
13
|
+
::Digest::SHA256.hexdigest(src),
|
14
|
+
::Digest::SHA256.hexdigest(dest),
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid_card?(number)
|
19
|
+
numbers = number.to_s.chars.map(&:to_i)
|
20
|
+
|
21
|
+
modified_numbers = numbers.reverse.map.with_index do |number, index|
|
22
|
+
if index.odd?
|
23
|
+
number *= 2
|
24
|
+
number -= 9 if number > 9
|
25
|
+
end
|
26
|
+
|
27
|
+
number
|
28
|
+
end
|
29
|
+
|
30
|
+
(modified_numbers.sum % 10).zero?
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid_email?(email)
|
34
|
+
email.to_s =~ EMAIL_REGEXP
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid_card_holder?(holder)
|
38
|
+
words = holder.to_s.split
|
39
|
+
return if words.count != 2
|
40
|
+
return if words.any? { |x| x.match?(/(.+)(\1)(\1)/) }
|
41
|
+
return unless words.all? { |x| x.size >= 2 }
|
42
|
+
return unless words.all? { |x| x.match?(/\A[A-Z]+\z/) }
|
43
|
+
return unless words.all? { |x| x.match?(VOWELS_REGEX) && x.match?(CONSONANTS_REGEX) }
|
44
|
+
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid_card_cvv?(cvv)
|
49
|
+
cvv = cvv.to_s.scan(/\d/).join
|
50
|
+
cvv.size.between?(3, 4)
|
51
|
+
end
|
52
|
+
|
53
|
+
def valid_phone?(phone)
|
54
|
+
Phonelib.valid?(phone)
|
55
|
+
end
|
56
|
+
|
57
|
+
def between?(checked_value, boundary_values, convert_sym: :to_f)
|
58
|
+
checked_value.public_send(convert_sym).between?(*boundary_values.first(2).map(&convert_sym))
|
59
|
+
end
|
60
|
+
|
61
|
+
def int_array?(value, size_range = 1..Float::INFINITY)
|
62
|
+
value.all? { |value| value.to_i.positive? } && value.size.in?(size_range)
|
63
|
+
end
|
64
|
+
|
65
|
+
def valid_limit?(limit)
|
66
|
+
int_array?(limit, 2..2) && limit.reduce(:<=)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Constants
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def get_class(*name_parts)
|
8
|
+
safe_constantize(name_parts.join("/").underscore.camelize)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_class!(*args)
|
12
|
+
get_class(*args) or raise "Failed to get class for #{args.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def safe_constantize(constant_name)
|
16
|
+
constant = suppress(NameError) { Object.const_get(constant_name, false) }
|
17
|
+
constant if constant && constant.name == constant_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def match_by_class!(**kwargs)
|
21
|
+
name, instance = kwargs.shift
|
22
|
+
result = kwargs.find { |klass, _| instance.is_a?(klass) }&.last
|
23
|
+
raise "Unsupported #{name} type: #{instance.inspect}" if result.nil?
|
24
|
+
|
25
|
+
result.is_a?(Proc) ? result.call : result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Control
|
5
|
+
extend self
|
6
|
+
|
7
|
+
class UniqueConstraintViolation < StandardError; end
|
8
|
+
|
9
|
+
def run_in_interval(interval, key:)
|
10
|
+
previous_string = Store[key]
|
11
|
+
previous = previous_string ? Time.zone.parse(previous_string) : Time.utc(0)
|
12
|
+
|
13
|
+
return if previous + interval > Time.current
|
14
|
+
Store[key] = Time.current
|
15
|
+
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
Store.delete(key) rescue nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def retry_on_unique_violation(
|
22
|
+
times: Float::INFINITY, retry_on_all_constraints: false, checked_constraints: [], &block
|
23
|
+
)
|
24
|
+
retry_on(Sequel::UniqueConstraintViolation, times: times) do
|
25
|
+
DB.transaction(savepoint: true, &block)
|
26
|
+
rescue Sequel::UniqueConstraintViolation => e
|
27
|
+
constraint_name = Database.get_violated_constraint_name(e)
|
28
|
+
|
29
|
+
if retry_on_all_constraints || checked_constraints.include?(constraint_name)
|
30
|
+
raise e
|
31
|
+
else
|
32
|
+
raise UniqueConstraintViolation, e.message
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def run_non_critical(rescue_all: false, in_transaction: false, &block)
|
38
|
+
in_transaction ? DB.transaction(savepoint: true, &block) : yield
|
39
|
+
rescue (rescue_all ? Exception : StandardError) => e
|
40
|
+
Exceptions.notify!(e)
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def retry_on(exception, times: Float::INFINITY, wait: 0)
|
45
|
+
retries = 0
|
46
|
+
|
47
|
+
begin
|
48
|
+
yield
|
49
|
+
rescue exception
|
50
|
+
retries += 1
|
51
|
+
raise if retries > times
|
52
|
+
sleep(wait)
|
53
|
+
retry
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Database
|
5
|
+
extend self
|
6
|
+
|
7
|
+
HandledConstaintError = Class.new(StandardError)
|
8
|
+
|
9
|
+
def handle_constraint_error(constraint_name, &block)
|
10
|
+
DB.transaction(savepoint: true, &block)
|
11
|
+
rescue Sequel::UniqueConstraintViolation => e
|
12
|
+
if constraint_name.to_s == get_violated_constraint_name(e)
|
13
|
+
raise HandledConstaintError
|
14
|
+
else
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_violated_constraint_name(exception)
|
20
|
+
error = exception.wrapped_exception
|
21
|
+
error.result.error_field(PG::Result::PG_DIAG_CONSTRAINT_NAME)
|
22
|
+
end
|
23
|
+
|
24
|
+
def each_record(dataset, **options, &block)
|
25
|
+
primary_key = primary_key_from(options)
|
26
|
+
with_temp_table(dataset, **options) do |ids|
|
27
|
+
dataset.model.where(primary_key => ids).each(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_temp_table(dataset, **options)
|
32
|
+
primary_key = primary_key_from(options)
|
33
|
+
page_size = options.fetch(:page_size, 1_000)
|
34
|
+
do_sleep = options.fetch(:sleep, Rails.env.production?)
|
35
|
+
|
36
|
+
temp_table_name = create_temp_table(dataset, primary_key: primary_key)
|
37
|
+
|
38
|
+
pk_set = []
|
39
|
+
|
40
|
+
loop do
|
41
|
+
DB.transaction do
|
42
|
+
pk_expr = DB[temp_table_name].select(primary_key).reverse(primary_key).limit(page_size)
|
43
|
+
|
44
|
+
deleted_items = DB[temp_table_name].where(primary_key => pk_expr).returning.delete
|
45
|
+
pk_set = deleted_items.map { |item| item[primary_key] }
|
46
|
+
|
47
|
+
yield(pk_set) if pk_set.any?
|
48
|
+
end
|
49
|
+
|
50
|
+
break if pk_set.empty?
|
51
|
+
|
52
|
+
sleep(1) if do_sleep
|
53
|
+
clear_lamian_logs!
|
54
|
+
end
|
55
|
+
ensure
|
56
|
+
DB.drop_table(temp_table_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def clear_lamian_logs!
|
60
|
+
Lamian.logger.send(:logdevs).each { |x| x.truncate(0) && x.rewind }
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_temp_table(dataset, primary_key:)
|
64
|
+
model = dataset.model
|
65
|
+
time = Time.current
|
66
|
+
temp_table_name = "temp_#{model.table_name}_#{time.to_i}#{time.nsec}".to_sym
|
67
|
+
type = model.db_schema[primary_key][:db_type]
|
68
|
+
|
69
|
+
DB.drop_table?(temp_table_name)
|
70
|
+
DB.create_table(temp_table_name) { column primary_key, type, primary_key: true }
|
71
|
+
|
72
|
+
insert_ds = dataset.select(Sequel[model.table_name][primary_key])
|
73
|
+
DB[temp_table_name].insert(insert_ds)
|
74
|
+
temp_table_name
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def primary_key_from(options)
|
80
|
+
options.fetch(:primary_key, :id)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Formatting
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def pluralize(symbol)
|
8
|
+
symbol.to_s.pluralize.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def merge_query_into_url(url, query)
|
12
|
+
uri = Addressable::URI.parse(url)
|
13
|
+
url = uri.omit(:query)
|
14
|
+
original_query = uri.query_values || {}
|
15
|
+
to_url(url, **original_query, **query.stringify_keys)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_url(*parts)
|
19
|
+
params = parts.select { |x| x.is_a?(Hash) }
|
20
|
+
parts -= params
|
21
|
+
params = params.reduce(&:merge)
|
22
|
+
query = to_query(params).presence if params.present?
|
23
|
+
[File.join(*parts), query].compact.join("?")
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_query(hash, namespace = nil)
|
27
|
+
pairs = hash.map do |key, value|
|
28
|
+
key = CGI.escape(key.to_s)
|
29
|
+
ns = namespace ? "#{namespace}[#{key}]" : key
|
30
|
+
value.is_a?(Hash) ? to_query(value, ns) : "#{CGI.escape(ns)}=#{CGI.escape(value.to_s)}"
|
31
|
+
end
|
32
|
+
|
33
|
+
pairs.join("&")
|
34
|
+
end
|
35
|
+
|
36
|
+
def uncapitalize_string(string)
|
37
|
+
string = string.dup
|
38
|
+
string[0] = string[0].downcase
|
39
|
+
string
|
40
|
+
end
|
41
|
+
|
42
|
+
def cache_key(*parts)
|
43
|
+
parts.flatten.compact.join("-")
|
44
|
+
end
|
45
|
+
|
46
|
+
def render_money(money)
|
47
|
+
"#{money.round} #{money.currency}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def match_or_nil(str, regex)
|
51
|
+
return if str.blank?
|
52
|
+
return unless str.match?(regex)
|
53
|
+
str
|
54
|
+
end
|
55
|
+
|
56
|
+
def encode_key(key)
|
57
|
+
Base64.strict_encode64(key.to_der)
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_date_part_string(part)
|
61
|
+
format("%<part>02d", part: part)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module UmbrellioUtils
|
6
|
+
class HTTPClient
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
def perform(*args, **kwargs)
|
10
|
+
client.perform(*args, **kwargs)
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform!(*args, **kwargs)
|
14
|
+
client.perform!(*args, **kwargs)
|
15
|
+
end
|
16
|
+
|
17
|
+
def request(*args, **kwargs)
|
18
|
+
client.request(*args, **kwargs)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def client
|
24
|
+
Thread.current[UmbrellioUtils.config.http_client_name] ||= EzClient.new(**ezclient_options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ezclient_options
|
28
|
+
{ keep_alive: 30, on_retry: method(:on_retry), timeout: 15 }
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_retry(_request, error, _metadata)
|
32
|
+
log!("Retrying on error: #{error.class}: #{error.message}")
|
33
|
+
end
|
34
|
+
|
35
|
+
def log!(message)
|
36
|
+
Rails.logger.info "[httpclient] #{message}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Misc
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def table_sync(scope, delay: 1, routing_key: nil)
|
8
|
+
scope.in_batches do |batch|
|
9
|
+
batch.each do |model|
|
10
|
+
next if model.try(:skip_table_sync?)
|
11
|
+
|
12
|
+
values = [model.class.name, model.values]
|
13
|
+
publisher = TableSync::Publishing::Publisher.new(*values, confirm: false)
|
14
|
+
publisher.routing_key = routing_key if routing_key
|
15
|
+
publisher.publish_now
|
16
|
+
end
|
17
|
+
|
18
|
+
sleep delay
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Ranges go from high to low priority
|
23
|
+
def merge_ranges(*ranges)
|
24
|
+
ranges = ranges.map { |x| x.present? && x.size == 2 ? x : [nil, nil] }
|
25
|
+
ranges.first.zip(*ranges[1..]).map { |x| x.find(&:present?) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Parsing
|
5
|
+
extend self
|
6
|
+
|
7
|
+
RFC_AUTH_HEADERS = %w[
|
8
|
+
HTTP_AUTHORIZATION
|
9
|
+
HTTP_X_HTTP_AUTHORIZATION
|
10
|
+
HTTP_REDIRECT_X_HTTP_AUTHORIZATION
|
11
|
+
].freeze
|
12
|
+
CARD_TRUNCATED_PAN_REGEX = /\A(\d{6}).*(\d{4})\z/.freeze
|
13
|
+
|
14
|
+
def try_to_parse_as_json(data)
|
15
|
+
JSON.parse(data) rescue data
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_xml(xml, remove_attributes: true, snakecase: true)
|
19
|
+
xml = Nokogiri::XML(xml)
|
20
|
+
xml.remove_namespaces!
|
21
|
+
xml.xpath("//@*").remove if remove_attributes
|
22
|
+
|
23
|
+
tags_converter = snakecase ? -> (tag) { tag.snakecase.to_sym } : -> (tag) { tag.to_sym }
|
24
|
+
nori = Nori.new(convert_tags_to: tags_converter)
|
25
|
+
nori.parse(xml.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION))
|
26
|
+
end
|
27
|
+
|
28
|
+
def card_truncated_pan(string)
|
29
|
+
string.gsub(CARD_TRUNCATED_PAN_REGEX, "\\1...\\2")
|
30
|
+
end
|
31
|
+
|
32
|
+
def card_expiry_time(string, year_format: "%y")
|
33
|
+
format_string = "%m/#{year_format}"
|
34
|
+
time = suppress(ArgumentError) { Time.zone.strptime(string, format_string) }
|
35
|
+
time + 1.month - 1.second if time
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_host(string)
|
39
|
+
URI(string).host
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_basic_auth(headers)
|
43
|
+
auth_header = headers.values_at(*RFC_AUTH_HEADERS).compact.first or return
|
44
|
+
credentials_b64 = auth_header[/\ABasic (.*)/, 1] or return
|
45
|
+
joined_credentials = Base64.strict_decode64(credentials_b64) rescue return
|
46
|
+
|
47
|
+
joined_credentials.split(":")
|
48
|
+
end
|
49
|
+
|
50
|
+
def safely_parse_base64(string)
|
51
|
+
Base64.strict_decode64(string)
|
52
|
+
rescue ArgumentError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def safely_parse_json(string)
|
57
|
+
JSON.parse(string)
|
58
|
+
rescue JSON::ParserError
|
59
|
+
{}
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_datetime(timestamp, timezone: "UTC", format: nil)
|
63
|
+
return if timestamp.blank?
|
64
|
+
tz = ActiveSupport::TimeZone[timezone]
|
65
|
+
format ? tz.strptime(timestamp, format) : tz.parse(timestamp)
|
66
|
+
end
|
67
|
+
|
68
|
+
def sanitize_phone(string, e164_format: false)
|
69
|
+
phone = Phonelib.parse(string)
|
70
|
+
return if phone.invalid?
|
71
|
+
return phone.e164 if e164_format
|
72
|
+
|
73
|
+
phone.sanitized
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Passwords
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def check(hash, password)
|
8
|
+
SCrypt::Password.new(hash).is_password?(password)
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_hash(password)
|
12
|
+
SCrypt::Password.create(password).to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
class RequestWrapper
|
5
|
+
include Memery
|
6
|
+
|
7
|
+
def initialize(request)
|
8
|
+
self.request = request
|
9
|
+
end
|
10
|
+
|
11
|
+
memoize def params
|
12
|
+
parse_params
|
13
|
+
end
|
14
|
+
|
15
|
+
memoize def body
|
16
|
+
request.body.read.dup.force_encoding("utf-8")
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
params[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
def rails_params
|
24
|
+
request.params
|
25
|
+
end
|
26
|
+
|
27
|
+
def raw_request
|
28
|
+
request
|
29
|
+
end
|
30
|
+
|
31
|
+
memoize def http_headers
|
32
|
+
headers = request.headers.select do |key, _value|
|
33
|
+
key.start_with?("HTTP_") || key.in?(ActionDispatch::Http::Headers::CGI_VARIABLES)
|
34
|
+
end
|
35
|
+
|
36
|
+
HTTP::Headers.coerce(headers.sort)
|
37
|
+
end
|
38
|
+
|
39
|
+
memoize def path_parameters
|
40
|
+
request.path_parameters.except(:controller, :action).stringify_keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def headers
|
44
|
+
request.headers
|
45
|
+
end
|
46
|
+
|
47
|
+
def ip
|
48
|
+
request.ip
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_accessor :request
|
54
|
+
|
55
|
+
def parse_params
|
56
|
+
case request.content_type
|
57
|
+
when "application/json"
|
58
|
+
Parsing.safely_parse_json(body)
|
59
|
+
when "application/xml"
|
60
|
+
Parsing.parse_xml(body)
|
61
|
+
else
|
62
|
+
request.get? ? request.GET : request.POST
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Rounding
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def fancy_round(number, rounding_method: :round, ugliness_level: 1)
|
8
|
+
return 0 unless number.positive?
|
9
|
+
log = Math.log(number, 10).floor
|
10
|
+
coef = 2**ugliness_level
|
11
|
+
(number * coef).public_send(rounding_method, -log) / coef.to_f
|
12
|
+
end
|
13
|
+
|
14
|
+
def super_round(number, rounding_method: :round)
|
15
|
+
return 0 unless number.positive?
|
16
|
+
|
17
|
+
coef = 10**Math.log(number, 10).floor
|
18
|
+
num = number / coef.to_f
|
19
|
+
|
20
|
+
best_diff = best_target = nil
|
21
|
+
|
22
|
+
[1.0, 1.5, 2.5, 5.0, 10.0].each do |target|
|
23
|
+
diff = target - num
|
24
|
+
|
25
|
+
next if rounding_method == :ceil && diff.negative?
|
26
|
+
next if rounding_method == :floor && diff.positive?
|
27
|
+
|
28
|
+
if best_diff.nil? || diff.abs < best_diff
|
29
|
+
best_diff = diff.abs
|
30
|
+
best_target = target
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
(best_target.to_d * coef).to_f
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Store
|
5
|
+
extend self
|
6
|
+
|
7
|
+
include Memery
|
8
|
+
|
9
|
+
def []=(key, value)
|
10
|
+
attrs = { key: key.to_s, value: JSON.dump(value), updated_at: Time.current }
|
11
|
+
entry.upsert_dataset.insert(attrs)
|
12
|
+
clear_cache_for(key)
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
find(key)&.value
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(key)
|
20
|
+
result = !!find(key)&.delete
|
21
|
+
clear_cache_for(key) if result
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
def find(key)
|
26
|
+
Rails.cache.fetch(cache_key_for(key)) { entry[key.to_s] }
|
27
|
+
end
|
28
|
+
|
29
|
+
memoize def entry
|
30
|
+
Sequel::Model(UmbrellioUtils.config.store_table_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def cache_key_for(key)
|
36
|
+
"store-entry-#{key}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear_cache_for(key)
|
40
|
+
Rails.cache.delete(cache_key_for(key))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module UmbrellioUtils
|
4
|
+
module Vault
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def secret_engine_present?(engine_path)
|
8
|
+
::Vault.logical.read("sys/mounts").data.key?("#{engine_path}/".to_sym)
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_kv_engine(path)
|
12
|
+
::Vault.logical.write(
|
13
|
+
"sys/mounts/#{path}",
|
14
|
+
config: {},
|
15
|
+
generate_signing_key: true,
|
16
|
+
options: { version: 2 },
|
17
|
+
path: path.to_s,
|
18
|
+
type: "kv",
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_to_kv(engine_path:, secret_path:, data:)
|
23
|
+
full_data_path = File.join(engine_path, "data", secret_path)
|
24
|
+
full_meta_path = File.join(engine_path, "metadata", secret_path)
|
25
|
+
::Vault.logical.write(full_data_path, data: data)
|
26
|
+
::Vault.logical.write(full_meta_path, id: secret_path, max_versions: 1, cas_required: false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/umbrellio_utils/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "umbrellio-utils"
|
7
|
+
spec.version = UmbrellioUtils::VERSION
|
8
|
+
spec.authors = ["JustAnotherDude"]
|
9
|
+
spec.email = ["VanyaZ158@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "A set of utilities that speed up development"
|
12
|
+
spec.description = "UmbrellioUtils is collection of utility classes and helpers"
|
13
|
+
spec.homepage = "https://github.com/umbrellio/utils"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/umbrellio/utils"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_dependency "memery"
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler"
|
32
|
+
spec.add_development_dependency "bundler-audit"
|
33
|
+
spec.add_development_dependency "ci-helper"
|
34
|
+
spec.add_development_dependency "pry"
|
35
|
+
spec.add_development_dependency "rake"
|
36
|
+
spec.add_development_dependency "rspec"
|
37
|
+
spec.add_development_dependency "rubocop-config-umbrellio"
|
38
|
+
spec.add_development_dependency "simplecov"
|
39
|
+
spec.add_development_dependency "simplecov-lcov"
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: umbrellio-utils
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JustAnotherDude
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-04-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: memery
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler-audit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ci-helper
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop-config-umbrellio
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov-lcov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: UmbrellioUtils is collection of utility classes and helpers
|
154
|
+
email:
|
155
|
+
- VanyaZ158@gmail.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- ".github/workflows/test.yml"
|
161
|
+
- ".gitignore"
|
162
|
+
- ".rspec"
|
163
|
+
- ".rubocop.yml"
|
164
|
+
- Gemfile
|
165
|
+
- LICENSE.txt
|
166
|
+
- README.md
|
167
|
+
- Rakefile
|
168
|
+
- bin/console
|
169
|
+
- bin/setup
|
170
|
+
- lib/umbrellio-utils.rb
|
171
|
+
- lib/umbrellio_utils.rb
|
172
|
+
- lib/umbrellio_utils/cards.rb
|
173
|
+
- lib/umbrellio_utils/checks.rb
|
174
|
+
- lib/umbrellio_utils/constants.rb
|
175
|
+
- lib/umbrellio_utils/control.rb
|
176
|
+
- lib/umbrellio_utils/database.rb
|
177
|
+
- lib/umbrellio_utils/formatting.rb
|
178
|
+
- lib/umbrellio_utils/http_client.rb
|
179
|
+
- lib/umbrellio_utils/misc.rb
|
180
|
+
- lib/umbrellio_utils/parsing.rb
|
181
|
+
- lib/umbrellio_utils/passwords.rb
|
182
|
+
- lib/umbrellio_utils/random.rb
|
183
|
+
- lib/umbrellio_utils/request_wrapper.rb
|
184
|
+
- lib/umbrellio_utils/rounding.rb
|
185
|
+
- lib/umbrellio_utils/store.rb
|
186
|
+
- lib/umbrellio_utils/vault.rb
|
187
|
+
- lib/umbrellio_utils/version.rb
|
188
|
+
- umbrellio_utils.gemspec
|
189
|
+
homepage: https://github.com/umbrellio/utils
|
190
|
+
licenses:
|
191
|
+
- MIT
|
192
|
+
metadata:
|
193
|
+
homepage_uri: https://github.com/umbrellio/utils
|
194
|
+
source_code_uri: https://github.com/umbrellio/utils
|
195
|
+
post_install_message:
|
196
|
+
rdoc_options: []
|
197
|
+
require_paths:
|
198
|
+
- lib
|
199
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
200
|
+
requirements:
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: 2.6.0
|
204
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
requirements: []
|
210
|
+
rubygems_version: 3.2.15
|
211
|
+
signing_key:
|
212
|
+
specification_version: 4
|
213
|
+
summary: A set of utilities that speed up development
|
214
|
+
test_files: []
|