more_core_extensions 3.6.0 → 4.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 605b41f75443bf69e235fccd80b9b94c48cbf44f95e5205ea540d5b36a84a75d
4
- data.tar.gz: 8cd816257ef3a0e564960273a65415173c207a962fe0aa8e681b4b83565edc12
3
+ metadata.gz: d8d52470776addb4edcca50184d26029d46a6a513cbc51125d539816974c2758
4
+ data.tar.gz: a0bbdb4f9f454eef6881cc85babc15523dcab44c1d5516f67aeae0037b69491b
5
5
  SHA512:
6
- metadata.gz: a6300d6be1bc10872dbc485160f1c51a2b388fa687e160944712d27d2ef12f41a98e122697855ff8fca3ea87a9f2f2b3d4cfacc9e83286d0f3559cf4375a56a9
7
- data.tar.gz: 76554a13bfcce20c1a7b361185f7215c552e7306b747168b8d3a21ae19d0908b6d02d30c91f127f1823f3bee5d5a5143721972e3d3252ece8f6ccd5e5ebe284e
6
+ metadata.gz: f1bc6f58dffd72b60a0746ad568bf9b7a4dbc0c7e0b742f9546ea10b75a54f9a098938726cde53fcfcec1a810b173d20d164ee15c0d570cac2033a785c702ca1
7
+ data.tar.gz: b301d6ed331a268b9ecf6d5e7b8ea0afdfb4b2a6333dc1eb8886e5a496be807bdc43a9c9335e6f514ac32ba3717bc8e8068cc4cf4feecd007ed8a77f21d27677
@@ -1,15 +1,15 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "2.0"
4
3
  - "2.1"
5
4
  - "2.2"
6
- - "2.3.3"
7
- - "2.4.0"
5
+ - "2.3"
6
+ - "2.4"
7
+ - "2.5.8"
8
+ - "2.6.6"
9
+ - "2.7.1"
8
10
  - ruby-head
9
11
  - jruby-head
10
- sudo: false
11
12
  cache: bundler
12
- before_install: gem install bundler -v ">= 1.13.6"
13
13
  after_script: bundle exec codeclimate-test-reporter
14
14
  matrix:
15
15
  allow_failures:
@@ -4,6 +4,42 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [4.2.0] - 2020-07-20
8
+ ### Added
9
+ - Add bundler-inject allowing developers to override dependencies [[#89](https://github.com/ManageIQ/more_core_extensions/pull/89)]
10
+ - Add Array and Hash #deep_clone and #deep_delete [[#91](https://github.com/ManageIQ/more_core_extensions/pull/91)]
11
+ - Add Digest::UUID.clean to properly format UUID strings [[#81](https://github.com/ManageIQ/more_core_extensions/pull/81)]
12
+
13
+ ###Changed
14
+ - Update ArrayTableize to properly set field width for color text [[#87](https://github.com/ManageIQ/more_core_extensions/pull/87)]
15
+ - Change Array#format_table header output to markdown vs postgres [[#83](https://github.com/ManageIQ/more_core_extensions/pull/83)]
16
+
17
+ ## [4.1.0] - 2020-04-30
18
+ ### Added
19
+ - Added Ruby 2.7 support [[#79](https://github.com/ManageIQ/more_core_extensions/pull/79)]
20
+ - Added Process#pause, Process#resume, and Process#alive? [[#73](https://github.com/ManageIQ/more_core_extensions/pull/73)]
21
+
22
+ ## [4.0.0] - 2020-01-31
23
+ ### Changed
24
+ - **BREAKING**: Moved Object#descendant_get to Class#descendant_get [[#75](https://github.com/ManageIQ/more_core_extensions/pull/75)]
25
+ - **BREAKING**: Removed deprecated Enumerable#stable_sort_by [[#76](https://github.com/ManageIQ/more_core_extensions/pull/76)]
26
+
27
+ ## [3.8.0] - 2020-01-31
28
+ ### Changed
29
+ - Renamed Enumerable#stable_sort_by to Array#tabular_sort [[#68](https://github.com/ManageIQ/more_core_extensions/pull/68)]
30
+ - Deprecated Enumerable#stable_sort_by [[#74](https://github.com/ManageIQ/more_core_extensions/pull/74)]
31
+
32
+ ### Added
33
+ - Added Class#leaf_subclasses [[#71](https://github.com/ManageIQ/more_core_extensions/pull/71)]
34
+ - Added Array#compact_map [[#63](https://github.com/ManageIQ/more_core_extensions/pull/63)]
35
+
36
+ ## [3.7.0] - 2019-02-04
37
+ ### Added
38
+ - Added Enumerable#stable_sort_by [[#67](https://github.com/ManageIQ/more_core_extensions/pull/67)]
39
+ - Added Math#slope_y_intercept, #slope_x_intercept, #linear_regression [[#50](https://github.com/ManageIQ/more_core_extensions/pull/50)]
40
+ - Added Benchmark#realtime_store, #realtime_block and helper methods [[#65](https://github.com/ManageIQ/more_core_extensions/pull/65)]
41
+ - Added Class#hierarchy and #lineage [[#61](https://github.com/ManageIQ/more_core_extensions/pull/61)]
42
+
7
43
  ## [3.6.0] - 2018-03-01
8
44
  ### Added
9
45
  - Added String#decimal_si_to_big_decimal [[#59](https://github.com/ManageIQ/more_core_extensions/pull/59)]
@@ -61,7 +97,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
61
97
  - Upgraded to RSpec 3 [[#16](https://github.com/ManageIQ/more_core_extensions/pull/16)]
62
98
  - Added the Change Log!
63
99
 
64
- [Unreleased]: https://github.com/ManageIQ/more_core_extensions/compare/v3.6.0...HEAD
100
+ [Unreleased]: https://github.com/ManageIQ/more_core_extensions/compare/v4.2.0...HEAD
101
+ [4.2.0]: https://github.com/ManageIQ/more_core_extensions/compare/v4.1.0...v4.2.0
102
+ [4.1.0]: https://github.com/ManageIQ/more_core_extensions/compare/v4.0.0...v4.1.0
103
+ [4.0.0]: https://github.com/ManageIQ/more_core_extensions/compare/v3.8.0...v4.0.0
104
+ [3.8.0]: https://github.com/ManageIQ/more_core_extensions/compare/v3.7.0...v3.8.0
105
+ [3.7.0]: https://github.com/ManageIQ/more_core_extensions/compare/v3.6.0...v3.7.0
65
106
  [3.6.0]: https://github.com/ManageIQ/more_core_extensions/compare/v3.5.0...v3.6.0
66
107
  [3.5.0]: https://github.com/ManageIQ/more_core_extensions/compare/v3.4.0...v3.5.0
67
108
  [3.4.0]: https://github.com/ManageIQ/more_core_extensions/compare/v3.3.0...v3.4.0
data/Gemfile CHANGED
@@ -1,8 +1,16 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ plugin 'bundler-inject'
4
+ require File.join(Bundler::Plugin.index.load_paths("bundler-inject")[0], "bundler-inject") rescue nil
5
+
3
6
  # Specify your gem's dependencies in more_core_extensions.gemspec
4
7
  gemspec
5
8
 
6
- # HACK: Rails 5 dropped support for Ruby < 2.2.2
7
- active_support_version = "< 5" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.2.2")
9
+ # Rails 5 dropped support for Ruby < 2.2.2
10
+ # Rails 6 dropped support for Ruby < 2.4.4
11
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.2.2")
12
+ active_support_version = "< 5"
13
+ elsif Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.4.4")
14
+ active_support_version = "< 6"
15
+ end
8
16
  gem 'activesupport', active_support_version
data/README.md CHANGED
@@ -14,9 +14,12 @@ MoreCoreExtensions are a set of core extensions beyond those provided by ActiveS
14
14
 
15
15
  #### Array
16
16
 
17
+ * core_ext/array/compact_map.rb
18
+ * `#compact_map` - Collect non-nil results from the block
17
19
  * core_ext/array/deletes.rb
18
20
  * `#delete_blanks` - Deletes all items where the value is blank
19
21
  * `#delete_nils` - Deletes all items where the value is nil
22
+ * `#deep_delete` - Deletes nested hash key elements
20
23
  * core_ext/array/duplicates.rb
21
24
  * `#duplicates` - Returns an Array of the duplicates found
22
25
  * core_ext/array/element_counts.rb
@@ -35,6 +38,8 @@ MoreCoreExtensions are a set of core extensions beyond those provided by ActiveS
35
38
  * core_ext/array/random.rb
36
39
  * `#random_index` - Picks a valid index randomly
37
40
  * `#random_element` - Picks an element randomly
41
+ * core_ext/array/sorting.rb
42
+ * `#tabular_sort` - Sorts an Array of Hashes by specific columns
38
43
  * core_ext/array/stretch.rb
39
44
  * `.stretch` - Stretch all argument Arrays to make them the same size
40
45
  * `.stretch!` - Stretch all argument Arrays to make them the same size. Modifies the arguments in place.
@@ -44,11 +49,20 @@ MoreCoreExtensions are a set of core extensions beyond those provided by ActiveS
44
49
  * core_ext/array/tableize.rb
45
50
  * `#tableize` - Create a string representation of receiver in a tabular format if receiver is an Array of Arrays or an Array of Hashes
46
51
 
52
+ #### Class
53
+
54
+ * core_ext/class/hierarchy.rb
55
+ * `#descendant_get` - Returns the descendant with a given name
56
+ * `#hierarchy` - Returns a tree-like Hash structure of all descendants.
57
+ * `#lineage` - Returns an Array of all superclasses.
58
+ * `#leaf_subclasses` - Returns an Array of all descendants which have no subclasses.
59
+
47
60
  #### Hash
48
61
 
49
62
  * core_ext/hash/deletes.rb
50
63
  * `#delete_blanks` - Deletes all keys where the value is blank
51
64
  * `#delete_nils` - Deletes all keys where the value is nil
65
+ * `#deep_delete` - Deletes nested hash key elements
52
66
  * core_ext/hash/nested.rb (see [Shared](#shared))
53
67
  * `#delete_blank_paths` - Deletes all paths where the value is blank
54
68
  * core_ext/hash/sorting.rb (see [Shared](#shared))
@@ -75,11 +89,16 @@ MoreCoreExtensions are a set of core extensions beyond those provided by ActiveS
75
89
 
76
90
  #### Object
77
91
 
78
- * core_ext/module/descendants.rb
79
- * `#descendant_get` - Returns the descendant with a given name
80
92
  * core_ext/module/namespace.rb
81
93
  * `#in_namespace?` - Returns whether or not the object is in the given namespace
82
94
 
95
+ #### Process
96
+
97
+ * core_ext/process/pause_resume.rb
98
+ * `.pause` - Pauses a process
99
+ * `.resume` - Resumes a paused process
100
+ * `.alive?` - Returns whether or not a process is running
101
+
83
102
  #### Range
84
103
 
85
104
  * core_ext/range/step_value.rb
@@ -112,6 +131,7 @@ MoreCoreExtensions are a set of core extensions beyond those provided by ActiveS
112
131
  #### Shared
113
132
 
114
133
  * core_ext/shared/nested.rb
134
+ * `#deep_clone` - Performs a Marshal based deep clone
115
135
  * `#delete_path` - Delete the value at the specified nesting
116
136
  * `#fetch_path` - Fetch the value at the specified nesting
117
137
  * `#find_path` - Detect which nesting holds the specified value
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "more_core_extensions"
4
+ require "more_core_extensions/all"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -1,10 +1,15 @@
1
1
  require 'more_core_extensions/version'
2
2
 
3
3
  require 'more_core_extensions/core_ext/array'
4
+ require 'more_core_extensions/core_ext/benchmark'
5
+ require 'more_core_extensions/core_ext/class'
6
+ require 'more_core_extensions/core_ext/digest'
4
7
  require 'more_core_extensions/core_ext/hash'
8
+ require 'more_core_extensions/core_ext/math'
5
9
  require 'more_core_extensions/core_ext/module'
6
10
  require 'more_core_extensions/core_ext/numeric'
7
11
  require 'more_core_extensions/core_ext/object'
12
+ require 'more_core_extensions/core_ext/process'
8
13
  require 'more_core_extensions/core_ext/range'
9
14
  require 'more_core_extensions/core_ext/string'
10
15
  require 'more_core_extensions/core_ext/symbol'
@@ -1,3 +1,4 @@
1
+ require 'more_core_extensions/core_ext/array/compact_map'
1
2
  require 'more_core_extensions/core_ext/array/deletes'
2
3
  require 'more_core_extensions/core_ext/array/duplicates'
3
4
  require 'more_core_extensions/core_ext/array/element_counts'
@@ -5,5 +6,6 @@ require 'more_core_extensions/core_ext/array/inclusions'
5
6
  require 'more_core_extensions/core_ext/array/math'
6
7
  require 'more_core_extensions/core_ext/array/nested'
7
8
  require 'more_core_extensions/core_ext/array/random'
9
+ require 'more_core_extensions/core_ext/array/sorting'
8
10
  require 'more_core_extensions/core_ext/array/stretch'
9
11
  require 'more_core_extensions/core_ext/array/tableize'
@@ -0,0 +1,19 @@
1
+ module MoreCoreExtensions
2
+ module ArrayCompactMap
3
+ # Collect non-nil results from the block. Basically [].collect { |i| ... }.compact
4
+ #
5
+ # [1,2,3,4,5].compact_map { |i| i * 2 if i.odd?} # => [2,6,10]
6
+ def compact_map
7
+ return enum_for(:compact_map) unless block_given?
8
+
9
+ [].tap do |results|
10
+ each do |i|
11
+ result = yield(i)
12
+ results << result if result
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Array.send(:include, MoreCoreExtensions::ArrayCompactMap)
@@ -15,6 +15,15 @@ module MoreCoreExtensions
15
15
  def delete_blanks
16
16
  delete_if { |i| i.blank? }
17
17
  end
18
+
19
+ # Deletes all keys and subkeys that match +key+.
20
+ #
21
+ # [{:a => {:b => 2, :c => 3}}].deep_delete(:b) # => [{:a => {:c => 3}}]
22
+ #
23
+ def deep_delete(key)
24
+ each { |i| i.deep_delete(key) if i.respond_to?(:deep_delete) }
25
+ self
26
+ end
18
27
  end
19
28
  end
20
29
 
@@ -0,0 +1,57 @@
1
+ module MoreCoreExtensions
2
+ module StableSorting
3
+ # Sorts an Array of Hashes by specific columns.
4
+ #
5
+ # Rows are sorted by +col_names+, if given, otherwise by the given block.
6
+ # The +order+ parameter can be given :ascending or :descending and
7
+ # defaults to :ascending.
8
+ #
9
+ # Note:
10
+ # - Strings are sorted case-insensitively
11
+ # - nil values are sorted last
12
+ # - Boolean values are sorted alphabetically (i.e. false then true)
13
+ #
14
+ # [
15
+ # {:col1 => 'b', :col2 => 2},
16
+ # {:col1 => 'b', :col2 => 1},
17
+ # {:col1 => 'A', :col2 => 1}
18
+ # ].tabular_sort([:col1, :col2])
19
+ #
20
+ # # => [
21
+ # # {:col1 => 'A', :col2 => 1},
22
+ # # {:col1 => 'b', :col2 => 1},
23
+ # # {:col1 => 'b', :col2 => 2}
24
+ # # ]
25
+ def tabular_sort(col_names = nil, order = nil, &block)
26
+ # stabilizer is needed because of
27
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/170565
28
+ stabilizer = 0
29
+ nil_rows, sortable =
30
+ partition do |r|
31
+ Array(col_names).any? { |c| r[c].nil? }
32
+ end
33
+
34
+ data_array =
35
+ if col_names
36
+ sortable.sort_by do |r|
37
+ stabilizer += 1
38
+ [Array(col_names).map do |col|
39
+ val = r[col]
40
+ val = val.downcase if val.kind_of?(String)
41
+ val = val.to_s if val.kind_of?(FalseClass) || val.kind_of?(TrueClass)
42
+ val
43
+ end, stabilizer]
44
+ end
45
+ else
46
+ sortable.sort_by(&block)
47
+ end.to_a
48
+
49
+ data_array += nil_rows
50
+
51
+ data_array.reverse! if order == :descending
52
+ data_array
53
+ end
54
+ end
55
+ end
56
+
57
+ Array.send(:include, MoreCoreExtensions::StableSorting)
@@ -53,6 +53,9 @@ module MoreCoreExtensions
53
53
 
54
54
  private
55
55
 
56
+ ANSI_ESCAPE_SEQUENCE = /\e\[[^m]+m/.freeze
57
+ ANSI_RESET = "\e[0m".freeze
58
+
56
59
  def tableize_hashes
57
60
  # Convert the target to an Array of Arrays
58
61
  keys = options[:columns] || columns_from_hash_keys
@@ -102,7 +105,7 @@ module MoreCoreExtensions
102
105
  end
103
106
 
104
107
  def apply_width!(widths, field, field_i)
105
- widths[field_i] = [widths[field_i].to_i, field.to_s.length].max
108
+ widths[field_i] = [widths[field_i].to_i, ansi_strip(field.to_s).length].max
106
109
  widths[field_i] = [options[:max_width], widths[field_i].to_i].min if options[:max_width]
107
110
  end
108
111
 
@@ -118,13 +121,44 @@ module MoreCoreExtensions
118
121
  end
119
122
 
120
123
  def format_field(field, width, justification)
121
- field = field.to_s.gsub(/\n|\r/, '').slice(0, width)
124
+ field = field.to_s.gsub(/\n|\r/, '')
125
+ field = ansi_truncate(field, width)
122
126
  "%0#{justification}#{width}s" % field
123
127
  end
124
128
 
129
+ def ansi_escapes?(field)
130
+ !!field.match(ANSI_ESCAPE_SEQUENCE)
131
+ end
132
+
133
+ def ansi_escapes(field)
134
+ field.to_enum(:scan, ANSI_ESCAPE_SEQUENCE).map { Regexp.last_match }
135
+ end
136
+
137
+ def ansi_strip(field)
138
+ field.gsub(ANSI_ESCAPE_SEQUENCE, '')
139
+ end
140
+
141
+ def ansi_truncate(field, width)
142
+ escapes = ansi_escapes(field)
143
+ if escapes.none?
144
+ field.slice(0, width)
145
+ else
146
+ escape_widths = 0
147
+ escapes.each do |e|
148
+ break if e.offset(0).first - escape_widths >= width
149
+
150
+ escape_widths += e[0].size
151
+ end
152
+
153
+ field = field.slice(0, width + escape_widths)
154
+ field << ANSI_RESET if ansi_escapes?(field) && !field.end_with?(ANSI_RESET)
155
+ field
156
+ end
157
+ end
158
+
125
159
  def format_table(table, widths)
126
160
  if options[:header] && table.size > 1
127
- header_separator = widths.collect { |w| "-" * (w + 2) }.join("+")
161
+ header_separator = widths.collect { |w| "-" * (w + 2) }.join("|")
128
162
  table.insert(1, header_separator)
129
163
  end
130
164
  table.join("\n") << "\n"
@@ -0,0 +1 @@
1
+ require 'more_core_extensions/core_ext/benchmark/realtime_store'
@@ -0,0 +1,107 @@
1
+ require 'benchmark'
2
+
3
+ module MoreCoreExtensions
4
+ module BenchmarkRealtimeStore
5
+ # Stores the elapsed real time used to execute the given block in the given
6
+ # hash for the given key and returns the result from the block. If the hash
7
+ # already has a value for that key, the time is accumulated.
8
+ #
9
+ # timings = {}
10
+ #
11
+ # Benchmark.realtime_store(timings, :sleep) { sleep 2; "foo" } # => "foo"
12
+ # timings # => {:sleep => 2.00}
13
+ #
14
+ # Benchmark.realtime_store(timings, :sleep) { sleep 2; "bar" } # => "bar"
15
+ # timings # => {:sleep => 4.00}
16
+ def realtime_store(hash, key)
17
+ ret = nil
18
+ r0 = Time.now
19
+ begin
20
+ ret = yield
21
+ ensure
22
+ r1 = Time.now
23
+ hash[key] = (hash[key] || 0) + (r1.to_f - r0.to_f)
24
+ end
25
+ ret
26
+ end
27
+
28
+ # Stores the elapsed real time used to execute the given block for the given
29
+ # key and returns the hash as well as the result from the block. The hash is
30
+ # stored globally, keyed on thread id, and is cleared once the topmost nested
31
+ # call completes. If the hash already has a value for that key, the time is
32
+ # accumulated.
33
+ #
34
+ # Benchmark.realtime_block(:sleep) do
35
+ # sleep 2
36
+ # "foo"
37
+ # end # => ["foo", {:sleep => 2.00}]
38
+ #
39
+ # Benchmark.realtime_block(:outer_sleep) do
40
+ # sleep 2
41
+ # Benchmark.realtime_block(:inner_sleep) { sleep 2 }
42
+ # "bar"
43
+ # end # => ["bar", {:inner_sleep => 2.00, :outer_sleep => 4.00}]
44
+ #
45
+ # Benchmark.realtime_block(:outer_sleep) do
46
+ # sleep 2
47
+ # 2.times do
48
+ # Benchmark.realtime_block(:inner_sleep) { sleep 2 }
49
+ # end
50
+ # "baz"
51
+ # end # => ["baz", {:inner_sleep => 4.00, :outer_sleep => 6.00}]
52
+ def realtime_block(key, &block)
53
+ hash = current_realtime
54
+
55
+ if in_realtime_block?
56
+ ret = realtime_store(hash, key, &block)
57
+ return ret, hash
58
+ else
59
+ begin
60
+ self.current_realtime = hash
61
+ begin
62
+ ret = realtime_store(hash, key, &block)
63
+ return ret, hash
64
+ rescue Exception => err # rubocop:disable Lint/RescueException
65
+ err.define_singleton_method(:timings) { hash } unless err.respond_to?(:timings)
66
+ raise
67
+ ensure
68
+ delete_current_realtime
69
+ end
70
+ ensure
71
+ # A second layer of protection in case Timeout::Error struck right after
72
+ # setting self.current_realtime, or right before `delete_current_realtime`.
73
+ # In those cases, current_realtime might (wrongly) still exist.
74
+ delete_current_realtime if in_realtime_block?
75
+ end
76
+ end
77
+ end
78
+
79
+ def in_realtime_block?
80
+ @@realtime_by_tid.key?(thread_unique_identifier)
81
+ end
82
+
83
+ def current_realtime
84
+ @@realtime_by_tid[thread_unique_identifier] || Hash.new(0)
85
+ end
86
+
87
+ def current_realtime=(hash)
88
+ @@realtime_by_tid[thread_unique_identifier] = hash
89
+ end
90
+
91
+ def delete_current_realtime
92
+ @@realtime_by_tid.delete(thread_unique_identifier)
93
+ end
94
+
95
+ private
96
+
97
+ def thread_unique_identifier
98
+ # Forks inherit the @@realtime_by_tid and parent/child Thread.current.object_id
99
+ # are equal, so we need to index into the hash with the pid too.
100
+ "#{Process.pid}-#{Thread.current.object_id}"
101
+ end
102
+
103
+ @@realtime_by_tid = {}
104
+ end
105
+ end
106
+
107
+ Benchmark.send(:extend, MoreCoreExtensions::BenchmarkRealtimeStore)
@@ -0,0 +1 @@
1
+ require 'more_core_extensions/core_ext/class/hierarchy'
@@ -0,0 +1,55 @@
1
+ require 'active_support/core_ext/class/subclasses'
2
+ require 'active_support/core_ext/object/try'
3
+
4
+ module MoreCoreExtensions
5
+ module ClassHierarchy
6
+ # Returns the descendant with a given name
7
+ #
8
+ # require 'socket'
9
+ # IO.descendant_get("IO")
10
+ # # => IO
11
+ # IO.descendant_get("BasicSocket")
12
+ # # => BasicSocket
13
+ # IO.descendant_get("IPSocket")
14
+ # # => IPSocket
15
+ def descendant_get(desc_name)
16
+ return self if desc_name == name || desc_name.nil?
17
+ klass = descendants.find { |desc| desc.name == desc_name }
18
+ raise ArgumentError, "#{desc_name} is not a descendant of #{name}" unless klass
19
+ klass
20
+ end
21
+
22
+ # Returns a tree-like Hash structure of all descendants.
23
+ #
24
+ # require 'socket'
25
+ # IO.hierarchy
26
+ # # => {BasicSocket=>
27
+ # # {Socket=>{},
28
+ # # IPSocket=>{TCPSocket=>{TCPServer=>{}}, UDPSocket=>{}},
29
+ # # UNIXSocket=>{UNIXServer=>{}}},
30
+ # # File=>{}}
31
+ def hierarchy
32
+ subclasses.each_with_object({}) { |k, h| h[k] = k.hierarchy }
33
+ end
34
+
35
+ # Returns an Array of all superclasses.
36
+ #
37
+ # require 'socket'
38
+ # TCPServer.lineage
39
+ # # => [TCPSocket, IPSocket, BasicSocket, IO, Object, BasicObject]
40
+ def lineage
41
+ superclass.nil? ? [] : superclass.lineage.unshift(superclass)
42
+ end
43
+
44
+ # Returns an Array of all descendants which have no subclasses
45
+ #
46
+ # require 'socket'
47
+ # BasicSocket.leaf_subclasses
48
+ # # => [Socket, TCPServer, UDPSocket, UNIXServer]
49
+ def leaf_subclasses
50
+ descendants.select { |d| d.subclasses.empty? }
51
+ end
52
+ end
53
+ end
54
+
55
+ Class.send(:include, MoreCoreExtensions::ClassHierarchy)
@@ -0,0 +1 @@
1
+ require 'more_core_extensions/core_ext/digest/uuid'
@@ -0,0 +1,26 @@
1
+ require 'active_support/core_ext/digest/uuid'
2
+
3
+ module Digest
4
+ module UUID
5
+ UUID_REGEX_FORMAT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.freeze
6
+
7
+ # Takes a UUID string of varying formats and cleans it. It will strip invalid characters,
8
+ # such as leading and trailing brackets as well as whitespace. The result is a lowercased,
9
+ # canonical UUID string.
10
+ #
11
+ # If the +guid+ argument is nil or blank, then nil is returned. If the +guid+ is already
12
+ # clean, then no additional cleaning occurs, and it is returned as-is.
13
+ #
14
+ # @param guid [String] A string that should more or less represent a UUID.
15
+ # @return [String] A lowercase v4 UUID string stripped of any extraneous characters.
16
+ #
17
+ def self.clean(guid)
18
+ return nil if guid.nil?
19
+ g = guid.to_s.downcase
20
+ return nil if g.strip.empty?
21
+ return g if g.length == 36 && g =~ UUID_REGEX_FORMAT
22
+ g.delete!('^0-9a-f')
23
+ g.sub!(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, '\1-\2-\3-\4-\5')
24
+ end
25
+ end
26
+ end
@@ -15,6 +15,16 @@ module MoreCoreExtensions
15
15
  def delete_blanks
16
16
  delete_if { |k, v| v.blank? }
17
17
  end
18
+
19
+ # Deletes all keys and subkeys that match +key+.
20
+ #
21
+ # {:a => {:b => 2, :c => 3}}.deep_delete(:b) # => {:a => {:c => 3}}
22
+ #
23
+ def deep_delete(key)
24
+ key = [key] unless key.kind_of?(Array)
25
+ key.each { |k| delete(k) }
26
+ each_value { |v| v.deep_delete(key) if v.respond_to?(:deep_delete) }
27
+ end
18
28
  end
19
29
  end
20
30
 
@@ -0,0 +1 @@
1
+ require 'more_core_extensions/core_ext/math/slope'
@@ -0,0 +1,54 @@
1
+ require 'more_core_extensions/core_ext/numeric/math'
2
+
3
+ module MoreCoreExtensions
4
+ module MathSlope
5
+ module ClassMethods
6
+ # Finds the y coordinate given x, slope of the line and the y intercept
7
+ #
8
+ # `y = mx + b`
9
+ # Where `m` is the slope of the line and `b` is the y intercept
10
+ #
11
+ # Math.slope_y_intercept(1, 0.5, 1) # => 1.5
12
+ def slope_y_intercept(x, slope, y_intercept)
13
+ slope * x + y_intercept
14
+ end
15
+
16
+ # Finds the x coordinate given y, slope of the line and the y intercept
17
+ #
18
+ # `x = (y - b) / m`
19
+ # Where `m` is the slope of the line and `b` is the y intercept
20
+ #
21
+ # Math.slope_x_intercept(1.5, 0.5, 1) # => 1.0
22
+ def slope_x_intercept(y, slope, y_intercept)
23
+ (y - y_intercept) / slope.to_f
24
+ end
25
+
26
+ # Finds the linear regression of the given coordinates. Coordinates should be given as x, y pairs.
27
+ #
28
+ # Returns the slope of the line, the y intercept, and the R-squared value.
29
+ #
30
+ # Math.linear_regression([1.0, 1.0], [2.0, 2.0]) # => [1.0, 0.0, 1.0]
31
+ def linear_regression(*coordinates)
32
+ return if coordinates.empty?
33
+
34
+ x_array, y_array = coordinates.transpose
35
+ sum_x = x_array.sum
36
+ sum_x2 = x_array.map(&:square).sum
37
+ sum_y = y_array.sum
38
+ sum_y2 = y_array.map(&:square).sum
39
+ sum_xy = coordinates.map { |x, y| x * y }.sum
40
+
41
+ n = coordinates.size.to_f
42
+ slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.square)
43
+ return if slope.nan?
44
+
45
+ y_intercept = (sum_y - slope * sum_x) / n
46
+ r_squared = (n * sum_xy - sum_x * sum_y) / Math.sqrt((n * sum_x2 - sum_x.square) * (n * sum_y2 - sum_y.square)) rescue nil
47
+
48
+ return slope, y_intercept, r_squared
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ Math.send(:extend, MoreCoreExtensions::MathSlope::ClassMethods)
@@ -1,3 +1,2 @@
1
1
  require 'active_support/core_ext/object/blank'
2
2
  require 'more_core_extensions/core_ext/object/namespace'
3
- require 'more_core_extensions/core_ext/object/descendants'
@@ -0,0 +1 @@
1
+ require 'more_core_extensions/core_ext/process/pause_resume'
@@ -0,0 +1,110 @@
1
+ module MoreCoreExtensions
2
+ module ProcessPauseResume
3
+ if Gem.win_platform?
4
+ require 'fiddle'
5
+ ntdll = Fiddle.dlopen('ntdll')
6
+ kernel32 = Fiddle.dlopen('kernel32')
7
+
8
+ NtSuspendProcess = Fiddle::Function.new(
9
+ ntdll['NtSuspendProcess'],
10
+ [Fiddle::TYPE_UINTPTR_T],
11
+ Fiddle::TYPE_INT
12
+ )
13
+
14
+ private_constant :NtSuspendProcess
15
+
16
+ NtResumeProcess = Fiddle::Function.new(
17
+ ntdll['NtResumeProcess'],
18
+ [Fiddle::TYPE_UINTPTR_T],
19
+ Fiddle::TYPE_INT
20
+ )
21
+
22
+ private_constant :NtResumeProcess
23
+
24
+ OpenProcess = Fiddle::Function.new(
25
+ kernel32['OpenProcess'],
26
+ [Fiddle::TYPE_LONG, Fiddle::TYPE_INT, Fiddle::TYPE_LONG],
27
+ Fiddle::TYPE_UINTPTR_T
28
+ )
29
+
30
+ private_constant :OpenProcess
31
+
32
+ CloseHandle = Fiddle::Function.new(
33
+ kernel32['CloseHandle'],
34
+ [Fiddle::TYPE_UINTPTR_T],
35
+ Fiddle::TYPE_INT
36
+ )
37
+
38
+ private_constant :CloseHandle
39
+
40
+ PROCESS_SUSPEND_RESUME = 0x00000800
41
+
42
+ private_constant :PROCESS_SUSPEND_RESUME
43
+ end
44
+
45
+ # Returns whether or not the given process is running.
46
+ #
47
+ def alive?(pid)
48
+ Process.kill(0, pid)
49
+ true
50
+ rescue Errno::ESRCH
51
+ false
52
+ end
53
+
54
+ # Suspend the process +pid+. If the process isn't running then this is no-op.
55
+ #
56
+ if Gem.win_platform?
57
+ def pause(pid)
58
+ return unless alive?(pid)
59
+
60
+ begin
61
+ handle = OpenProcess.call(PROCESS_SUSPEND_RESUME, 0, pid)
62
+
63
+ if handle == 0
64
+ raise SystemCallError, Fiddle.win32_last_error, "OpenProcess"
65
+ end
66
+
67
+ if NtSuspendProcess.call(handle) != 0
68
+ raise SystemCallError, Fiddle.win32_last_error, "NtSuspendProcess"
69
+ end
70
+ ensure
71
+ CloseHandle.call(handle) if handle
72
+ end
73
+ 1 # For cross-platform compatibility
74
+ end
75
+ else
76
+ def pause(pid)
77
+ Process.kill('STOP', pid) if alive?(pid)
78
+ end
79
+ end
80
+
81
+ # Resume the process +pid+. If the process isn't running then this is a no-op.
82
+ #
83
+ if Gem.win_platform?
84
+ def resume(pid)
85
+ return unless alive?(pid)
86
+
87
+ begin
88
+ handle = OpenProcess.call(PROCESS_SUSPEND_RESUME, 0, pid)
89
+
90
+ if handle == 0
91
+ raise SystemCallError, Fiddle.win32_last_error, "OpenProcess"
92
+ end
93
+
94
+ if NtResumeProcess.call(handle) != 0
95
+ raise SystemCallError, Fiddle.win32_last_error, "NtResumeProcess"
96
+ end
97
+ ensure
98
+ CloseHandle.call(handle) if handle
99
+ end
100
+ 1 # For cross-platform compatibility
101
+ end
102
+ else
103
+ def resume(pid)
104
+ Process.kill('CONT', pid) if alive?(pid)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ Process.send(:extend, MoreCoreExtensions::ProcessPauseResume)
@@ -100,6 +100,21 @@ module MoreCoreExtensions
100
100
  end
101
101
  []
102
102
  end
103
+
104
+ # Create a deep clone of the object. This is similar to deep_dup
105
+ # but uses a Marshal based approach instead.
106
+ #
107
+ # h1 = {:a => "hello"}
108
+ # h2 = h1.deep_clone
109
+ #
110
+ # h1[:a] << " world"
111
+ #
112
+ # h1[:a] # "hello world"
113
+ # h2[:a] # "hello"
114
+ #
115
+ def deep_clone
116
+ Marshal.load(Marshal.dump(self))
117
+ end
103
118
  end
104
119
  end
105
120
  end
@@ -1,3 +1,3 @@
1
1
  module MoreCoreExtensions
2
- VERSION = "3.6.0".freeze
2
+ VERSION = "4.2.0".freeze
3
3
  end
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.required_ruby_version = ">= 2.0.0"
24
24
 
25
- spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "bundler"
26
26
  spec.add_development_dependency "codeclimate-test-reporter"
27
27
  spec.add_development_dependency "rake"
28
28
  spec.add_development_dependency "rspec", ">= 3.0"
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "timecop"
31
31
 
32
32
  spec.add_runtime_dependency "activesupport"
33
+ spec.add_runtime_dependency "sync"
33
34
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: more_core_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Frey
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2018-03-01 00:00:00.000000000 Z
12
+ date: 2020-07-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "~>"
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '1.3'
20
+ version: '0'
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - "~>"
25
+ - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '1.3'
27
+ version: '0'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: codeclimate-test-reporter
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +109,20 @@ dependencies:
109
109
  - - ">="
110
110
  - !ruby/object:Gem::Version
111
111
  version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: sync
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
112
126
  description: MoreCoreExtensions are a set of core extensions beyond those provided
113
127
  by ActiveSupport.
114
128
  email:
@@ -133,6 +147,7 @@ files:
133
147
  - lib/more_core_extensions.rb
134
148
  - lib/more_core_extensions/all.rb
135
149
  - lib/more_core_extensions/core_ext/array.rb
150
+ - lib/more_core_extensions/core_ext/array/compact_map.rb
136
151
  - lib/more_core_extensions/core_ext/array/deletes.rb
137
152
  - lib/more_core_extensions/core_ext/array/duplicates.rb
138
153
  - lib/more_core_extensions/core_ext/array/element_counts.rb
@@ -140,12 +155,21 @@ files:
140
155
  - lib/more_core_extensions/core_ext/array/math.rb
141
156
  - lib/more_core_extensions/core_ext/array/nested.rb
142
157
  - lib/more_core_extensions/core_ext/array/random.rb
158
+ - lib/more_core_extensions/core_ext/array/sorting.rb
143
159
  - lib/more_core_extensions/core_ext/array/stretch.rb
144
160
  - lib/more_core_extensions/core_ext/array/tableize.rb
161
+ - lib/more_core_extensions/core_ext/benchmark.rb
162
+ - lib/more_core_extensions/core_ext/benchmark/realtime_store.rb
163
+ - lib/more_core_extensions/core_ext/class.rb
164
+ - lib/more_core_extensions/core_ext/class/hierarchy.rb
165
+ - lib/more_core_extensions/core_ext/digest.rb
166
+ - lib/more_core_extensions/core_ext/digest/uuid.rb
145
167
  - lib/more_core_extensions/core_ext/hash.rb
146
168
  - lib/more_core_extensions/core_ext/hash/deletes.rb
147
169
  - lib/more_core_extensions/core_ext/hash/nested.rb
148
170
  - lib/more_core_extensions/core_ext/hash/sorting.rb
171
+ - lib/more_core_extensions/core_ext/math.rb
172
+ - lib/more_core_extensions/core_ext/math/slope.rb
149
173
  - lib/more_core_extensions/core_ext/module.rb
150
174
  - lib/more_core_extensions/core_ext/module/cache_with_timeout.rb
151
175
  - lib/more_core_extensions/core_ext/module/namespace.rb
@@ -154,8 +178,9 @@ files:
154
178
  - lib/more_core_extensions/core_ext/numeric/math.rb
155
179
  - lib/more_core_extensions/core_ext/numeric/rounding.rb
156
180
  - lib/more_core_extensions/core_ext/object.rb
157
- - lib/more_core_extensions/core_ext/object/descendants.rb
158
181
  - lib/more_core_extensions/core_ext/object/namespace.rb
182
+ - lib/more_core_extensions/core_ext/process.rb
183
+ - lib/more_core_extensions/core_ext/process/pause_resume.rb
159
184
  - lib/more_core_extensions/core_ext/range.rb
160
185
  - lib/more_core_extensions/core_ext/range/step_value.rb
161
186
  - lib/more_core_extensions/core_ext/shared/nested.rb
@@ -187,8 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
212
  - !ruby/object:Gem::Version
188
213
  version: '0'
189
214
  requirements: []
190
- rubyforge_project:
191
- rubygems_version: 2.7.6
215
+ rubygems_version: 3.0.6
192
216
  signing_key:
193
217
  specification_version: 4
194
218
  summary: MoreCoreExtensions are a set of core extensions beyond those provided by
@@ -1,17 +0,0 @@
1
- require 'active_support/core_ext/class/subclasses'
2
-
3
- module MoreCoreExtensions
4
- module Descendants
5
- #
6
- # Retrieve a descendant by its name
7
- #
8
- def descendant_get(desc_name)
9
- return self if desc_name == name || desc_name.nil?
10
- klass = descendants.find { |desc| desc.name == desc_name }
11
- raise ArgumentError, "#{desc_name} is not a descendant of #{name}" unless klass
12
- klass
13
- end
14
- end
15
- end
16
-
17
- Object.send(:include, MoreCoreExtensions::Descendants)