foobara-util 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8d375fa882a7b34d39a4188c9c99b821770ab1edd6e843b78b9c4b5e3d755b4a
4
+ data.tar.gz: 4d8e3d0edd444bbc78139507cf8b7158e9d947717dd8dc1b83777393e62b3ca9
5
+ SHA512:
6
+ metadata.gz: 9fc109812e2f721642f6e9728f0cf942b5a75179f0b86e5cd8aeaac7a71339e07571aa055d8da7992bc87f411e42f5a7115a789fdf89daf38093264e3173ab2b
7
+ data.tar.gz: db0ac6f424dd18fdbf4a675d67caec1ac46b001513cc19278e7ead296f667331371d98850f6024e65f63b33b60c4304ae60d51808d717900df2f4bdbc1f5a6df
data/LICENSE.txt ADDED
@@ -0,0 +1,8 @@
1
+ foobara-util is dual licensed under your choice of the Apache-2.0 license and the MIT license.
2
+
3
+ Apache-2.0 License: LICENSE-APACHE.txt or https://www.apache.org/licenses/LICENSE-2.0.txt
4
+ MIT License: LICENSE-MIT.txt or https://opensource.org/licenses/MIT
5
+
6
+ This is equivalent to the following SPDX License Expression: Apache-2.0 OR MIT
7
+
8
+ Copyright (c) 2023 Miles Georgi
@@ -0,0 +1,65 @@
1
+ module Foobara
2
+ module TruncatedInspect
3
+ class << self
4
+ attr_accessor :truncated
5
+
6
+ def truncating(object)
7
+ if truncated
8
+ if truncated.include?(object)
9
+ "..."
10
+ else
11
+ truncated << object
12
+ yield
13
+ end
14
+ else
15
+ begin
16
+ self.truncated = Set[object]
17
+ yield
18
+ ensure
19
+ self.truncated = nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ MAX_LENGTH = 200
26
+
27
+ def inspect
28
+ TruncatedInspect.truncating(self) do
29
+ instance_vars = instance_variables.map do |var|
30
+ truncate = false
31
+
32
+ value = instance_variable_get(var)
33
+
34
+ is_array = value.is_a?(::Array)
35
+ is_hash = value.is_a?(::Hash)
36
+
37
+ if is_array
38
+ truncate = value.size > 10 || value.any? { |v| v.is_a?(::Array) || v.is_a?(::Hash) }
39
+ elsif is_hash
40
+ truncate = value.size > 10 || value.keys.any? { |k| !k.is_a?(::Symbol) && !k.is_a?(::String) }
41
+ truncate ||= value.values.any? { |v| v.is_a?(::Array) || v.is_a?(::Hash) }
42
+ end
43
+
44
+ value = if truncate
45
+ is_array ? "[...]" : "{...}"
46
+ else
47
+ value.inspect
48
+ end
49
+
50
+ "#{var}=#{value}"
51
+ end
52
+
53
+ instance_vars = instance_vars.join(", ")
54
+
55
+ result = "#<#{self.class.name}:0x#{object_id.to_s(16)} #{instance_vars}>"
56
+
57
+ if result.size > MAX_LENGTH
58
+ result = "#{result[0..(MAX_LENGTH - 5)]}...>"
59
+ end
60
+
61
+ result
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,105 @@
1
+ module Foobara
2
+ module Util
3
+ module_function
4
+
5
+ def args_and_opts_to_opts(args, opts)
6
+ unless args.is_a?(::Array)
7
+ # :nocov:
8
+ raise ArgumentError, "args must be an array of 0 or 1 hashes but received #{args}"
9
+ # :nocov:
10
+ end
11
+
12
+ unless opts.is_a?(::Hash)
13
+ # :nocov:
14
+ raise ArgumentError, "opts must be a hash not a #{opts.class}"
15
+ # :nocov:
16
+ end
17
+
18
+ case args.size
19
+ when 0
20
+ opts
21
+ when 1
22
+ arg = args.first
23
+
24
+ if opts && !opts.empty?
25
+ unless arg.is_a?(::Hash)
26
+ # :nocov:
27
+ raise ArgumentError, "opts must be a hash not a #{arg.class}"
28
+ # :nocov:
29
+ end
30
+
31
+ arg.merge(opts)
32
+ else
33
+ arg
34
+ end
35
+ else
36
+ # :nocov:
37
+ raise ArgumentError, "args must be an array of 0 or 1 hashes but received #{args}"
38
+ # :nocov:
39
+ end
40
+ end
41
+
42
+ def arg_and_opts_to_arg(arg, opts)
43
+ if arg && !arg.empty?
44
+ if opts && !opts.empty?
45
+ unless opts.is_a?(::Hash)
46
+ # :nocov:
47
+ raise ArgumentError, "opts must be a hash not a #{opts}"
48
+ # :nocov:
49
+ end
50
+
51
+ unless arg.is_a?(::Hash)
52
+ # :nocov:
53
+ raise ArgumentError, "arg must be a hash if present when opts is present"
54
+ # :nocov:
55
+ end
56
+
57
+ arg.merge(opts)
58
+ else
59
+ arg
60
+ end
61
+ elsif opts && !opts.empty?
62
+ opts
63
+ end
64
+ end
65
+
66
+ # Strange utility method here...
67
+ # idea is there's a method that takes 0 or 1 arguments which may or may not be a hash.
68
+ # To make sure we don't act like there's an argument when there's not or fail to combine
69
+ # parts of the hash that wind up in opts, this method comines it into an array of 0 or 1
70
+ # argument with stuff merged into the argument if needed.
71
+ def args_and_opts_to_args(args, opts)
72
+ unless args.is_a?(::Array)
73
+ # :nocov:
74
+ raise ArgumentError, "args must be an array of 0 or 1 hashes but received #{args}"
75
+ # :nocov:
76
+ end
77
+
78
+ case args.size
79
+ when 0
80
+ Util.array(arg_and_opts_to_arg(nil, opts))
81
+ when 1
82
+ # Do not go from 1 argument to 0. ie, [nil] should return [nil] not [].
83
+ if opts && !opts.empty?
84
+ arg = args.first
85
+
86
+ unless arg.is_a?(::Hash)
87
+ # :nocov:
88
+ raise ArgumentError, "Expected #{arg.inspect} to be a Hash"
89
+ # :nocov:
90
+ end
91
+
92
+ [arg_and_opts_to_arg(args.first, opts)]
93
+ else
94
+ args
95
+ end
96
+
97
+ [arg_and_opts_to_arg(args.first, opts)]
98
+ else
99
+ # :nocov:
100
+ raise ArgumentError, "args must be an array of 0 or 1 hashes but received #{args}"
101
+ # :nocov:
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,40 @@
1
+ module Foobara
2
+ module Util
3
+ module_function
4
+
5
+ def power_set(array)
6
+ return [[]] if array.empty?
7
+
8
+ head, *tail = array
9
+ subsets = power_set(tail)
10
+ subsets + subsets.map { |subset| [head, *subset] }
11
+ end
12
+
13
+ def array(object)
14
+ case object
15
+ when nil
16
+ []
17
+ when Array
18
+ object
19
+ else
20
+ [object]
21
+ end
22
+ end
23
+
24
+ def all_symbolic_elements?(array)
25
+ array.all? { |key| key.is_a?(Symbol) || key.is_a?(String) }
26
+ end
27
+
28
+ def all_symbolizable_elements?(array)
29
+ array.all? { |key| key.is_a?(Symbol) || key.is_a?(String) }
30
+ end
31
+
32
+ def all_blank_or_false?(array)
33
+ array.all? do |element|
34
+ element.nil? || element == false || (
35
+ (element.is_a?(::Hash) || element.is_a?(::Array) || element.is_a?(::String)) && element.empty?
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ module Foobara
2
+ module Util
3
+ module_function
4
+
5
+ def descendants(klass)
6
+ all = Set.new
7
+
8
+ klass.subclasses.each do |subclass|
9
+ all << subclass
10
+ all |= descendants(subclass)
11
+ end
12
+
13
+ all
14
+ end
15
+
16
+ # WARNING: This approach is known as being slow. Probably better for you class to track its own instances
17
+ # if this is being used for smoething more than debugging.
18
+ def instances(klass)
19
+ ObjectSpace.each_object(klass).to_a
20
+ end
21
+
22
+ # Kind of surprising that Ruby doesn't have a built in way to do this.
23
+ def super_method_of(current_instance, from_class, method_name)
24
+ method = current_instance.method(method_name)
25
+ method = method.super_method until method.owner == from_class
26
+ method.super_method
27
+ end
28
+
29
+ def super_method_takes_parameters?(current_instance, from_class, method_name)
30
+ super_method_of(current_instance, from_class, method_name).parameters.any?
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ module Foobara
2
+ module Util
3
+ module_function
4
+
5
+ def symbolize_keys(hash)
6
+ unless all_symbolizable_keys?(hash)
7
+ # :nocov:
8
+ raise "Cannot symbolize keys for #{hash} because they are not all symbolizable"
9
+ # :nocov:
10
+ end
11
+
12
+ hash.transform_keys(&:to_sym)
13
+ end
14
+
15
+ def symbolize_keys!(hash)
16
+ unless all_symbolizable_keys?(hash)
17
+ # :nocov:
18
+ raise "Cannot symbolize keys for #{hash} because they are not all symbolizable"
19
+ # :nocov:
20
+ end
21
+
22
+ hash.transform_keys!(&:to_sym)
23
+ end
24
+
25
+ def all_symbolic_keys?(hash)
26
+ all_symbolic_elements?(hash.keys)
27
+ end
28
+
29
+ def all_symbolizable_keys?(hash)
30
+ all_symbolizable_elements?(hash.keys)
31
+ end
32
+
33
+ def remove_empty(hash)
34
+ hash.reject { |_k, v| (v.is_a?(::Hash) || v.is_a?(::Array)) && v.empty? }
35
+ end
36
+
37
+ def remove_blank(hash)
38
+ remove_empty(hash).compact
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,83 @@
1
+ module Foobara
2
+ module Util
3
+ class ParentModuleDoesNotExistError < StandardError
4
+ attr_reader :name, :parent_name
5
+
6
+ def initialize(name:, parent_name:)
7
+ @name = name
8
+ @parent_name = parent_name
9
+
10
+ super("Parent module #{parent_name} does not exist for #{name}.")
11
+ end
12
+ end
13
+
14
+ module_function
15
+
16
+ def make_class(name, superclass = nil, which: :class, tag: false, &block)
17
+ name = name.to_s if name.is_a?(::Symbol)
18
+
19
+ if superclass.is_a?(Class)
20
+ superclass = superclass.name
21
+ end
22
+
23
+ inherit = superclass ? " < ::#{superclass}" : ""
24
+
25
+ superclass ||= :Object
26
+
27
+ name = name[2..] if name.start_with?("::")
28
+
29
+ already_exists = Object.const_defined?(name, false)
30
+
31
+ if !already_exists && tag
32
+ should_tag = true
33
+ end
34
+
35
+ parent_name = parent_module_name_for(name)
36
+
37
+ if parent_name && !Object.const_defined?(parent_name, false)
38
+ raise ParentModuleDoesNotExistError.new(name:, parent_name:)
39
+ end
40
+
41
+ unless already_exists
42
+ # rubocop:disable Security/Eval, Style/DocumentDynamicEvalDefinition
43
+ eval(<<~RUBY, binding, __FILE__, __LINE__ + 1)
44
+ #{which} ::#{name}#{inherit}
45
+ end
46
+ RUBY
47
+ # rubocop:enable Security/Eval, Style/DocumentDynamicEvalDefinition
48
+ end
49
+
50
+ klass = Object.const_get(name, false)
51
+
52
+ if should_tag
53
+ klass.instance_variable_set(:@foobara_created_via_make_class, true)
54
+ end
55
+
56
+ if block
57
+ if klass.is_a?(::Class)
58
+ klass.class_eval(&block)
59
+ else
60
+ klass.module_eval(&block)
61
+ end
62
+ end
63
+
64
+ klass
65
+ end
66
+
67
+ def make_class_p(name, superclass = nil, which: :class, tag: false, &)
68
+ make_class(name, superclass, which:, tag:, &)
69
+ rescue ParentModuleDoesNotExistError => e
70
+ make_class_p(e.parent_name, which: :module, tag:, &)
71
+ make_class(name, superclass, which:, tag:, &)
72
+ end
73
+
74
+ # TODO: Kind of weird that make_module is implemented in terms of make_class instead of the other way around
75
+ def make_module(name, &)
76
+ make_class(name, which: :module, &)
77
+ end
78
+
79
+ def make_module_p(name, tag: false, &)
80
+ make_class_p(name, which: :module, tag:, &)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,94 @@
1
+ module Foobara
2
+ module Util
3
+ module_function
4
+
5
+ def module_for(mod)
6
+ name = mod.name
7
+
8
+ return unless name
9
+
10
+ name = parent_module_name_for(mod.name)
11
+ Object.const_get(name) if name
12
+ end
13
+
14
+ def parent_module_name_for(module_name)
15
+ module_name[/(.*)::/, 1]
16
+ end
17
+
18
+ def non_full_name(mod)
19
+ name = if mod.is_a?(::String)
20
+ mod
21
+ else
22
+ mod.name
23
+ end
24
+
25
+ name&.[](/([^:]+)\z/, 1)
26
+ end
27
+
28
+ def non_full_name_underscore(mod)
29
+ underscore(non_full_name(mod))
30
+ end
31
+
32
+ def constant_value(mod, constant, inherit: false)
33
+ if mod.constants(inherit).include?(constant.to_sym)
34
+ mod.const_get(constant, inherit)
35
+ end
36
+ end
37
+
38
+ def constant_values(mod, is_a: nil, extends: nil, inherit: false)
39
+ if inherit && !mod.is_a?(Class)
40
+ # :nocov:
41
+ raise "Cannot pass inherit: true for something that is not a Class"
42
+ # :nocov:
43
+ end
44
+
45
+ if inherit
46
+ superklass = mod.superclass
47
+ values = constant_values(mod, is_a:, extends:)
48
+ if superklass == Object
49
+ values
50
+ else
51
+ [
52
+ *values,
53
+ *constant_values(superklass, is_a:, extends:, inherit:)
54
+ ]
55
+ end
56
+ else
57
+ is_a = Util.array(is_a)
58
+ extends = Util.array(extends)
59
+
60
+ mod.constants.map { |const| constant_value(mod, const) }.select do |object|
61
+ (is_a.nil? || is_a.empty? || is_a.any? { |klass| object.is_a?(klass) }) &&
62
+ (extends.nil? || extends.empty? || (object.is_a?(Class) && extends.any? do |klass|
63
+ object.ancestors.include?(klass)
64
+ end))
65
+ end.compact
66
+ end
67
+ end
68
+
69
+ def const_get_up_hierarchy(mod, name)
70
+ mod.const_get(name)
71
+ rescue NameError => e
72
+ if mod == Object || e.message !~ /uninitialized constant (.*::)?#{name}\z/
73
+ # :nocov:
74
+ raise
75
+ # :nocov:
76
+ end
77
+
78
+ mod = if mod.name&.include?("::")
79
+ module_for(mod)
80
+ else
81
+ Object
82
+ end
83
+
84
+ const_get_up_hierarchy(mod, name)
85
+ end
86
+
87
+ def remove_constant(const_name)
88
+ *path, name = const_name.split("::")
89
+ mod = path.inject(Object) { |m, constant| m.const_get(constant) }
90
+
91
+ mod.send(:remove_const, name)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,23 @@
1
+ module Foobara
2
+ module Util
3
+ module_function
4
+
5
+ def require_directory(directory)
6
+ require_pattern("#{directory}/**/*.rb")
7
+ end
8
+
9
+ def require_pattern(glob)
10
+ files = Dir[glob]
11
+
12
+ if files.empty?
13
+ # :nocov:
14
+ raise "Didn't find anything to require for #{glob}"
15
+ # :nocov:
16
+ end
17
+
18
+ files.sort_by { |file| [file.count("/"), file.length] }.reverse.each do |f|
19
+ require f
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,125 @@
1
+ module Foobara
2
+ module Util
3
+ module_function
4
+
5
+ def classify(string)
6
+ camelize(string, true)
7
+ end
8
+
9
+ def camelize(string, upcase_first = false)
10
+ return nil if string.nil?
11
+
12
+ if string.is_a?(::Symbol)
13
+ string = string.to_s
14
+ end
15
+
16
+ retval = ""
17
+
18
+ string.each_char do |char|
19
+ if char == "_"
20
+ upcase_first = true
21
+ elsif upcase_first
22
+ retval << char.upcase
23
+ upcase_first = false
24
+ else
25
+ retval << char.downcase
26
+ end
27
+ end
28
+
29
+ retval
30
+ end
31
+
32
+ IS_CAP = /[A-Z]/
33
+ IS_IDENTIFIER_CHARACTER = /\w/
34
+
35
+ def constantify(string)
36
+ return nil if string.nil?
37
+
38
+ if string.is_a?(::Symbol)
39
+ string = string.to_s
40
+ end
41
+
42
+ if string =~ /\A[A-Z_]*\z/
43
+ string.dup
44
+ else
45
+ underscore(string).upcase
46
+ end
47
+ end
48
+
49
+ def constantify_sym(string)
50
+ constantify(string)&.to_sym
51
+ end
52
+
53
+ def underscore(string)
54
+ return nil if string.nil?
55
+
56
+ if string.is_a?(::Symbol)
57
+ string = string.to_s
58
+ end
59
+
60
+ return "" if string.empty?
61
+
62
+ retval = ""
63
+ is_start = true
64
+
65
+ string.each_char do |char|
66
+ if IS_IDENTIFIER_CHARACTER =~ char
67
+ if IS_CAP =~ char
68
+ char = char.downcase
69
+ char = "_#{char}" unless is_start
70
+ is_start = false
71
+ elsif char == "_"
72
+ is_start = true
73
+ else
74
+ is_start = false
75
+ end
76
+ else
77
+ is_start = true
78
+ end
79
+
80
+ retval << char
81
+ end
82
+
83
+ retval
84
+ end
85
+
86
+ def kebab_case(string)
87
+ underscore(string)&.gsub("_", "-")
88
+ end
89
+
90
+ def underscore_sym(string)
91
+ underscore(string)&.to_sym
92
+ end
93
+
94
+ def to_sentence(strings, connector = ", ", last_connector = ", and ")
95
+ return "" if strings.empty?
96
+
97
+ *strings, last = strings
98
+
99
+ if strings.empty?
100
+ last
101
+ else
102
+ [strings.join(connector), last].join(last_connector)
103
+ end
104
+ end
105
+
106
+ def to_or_sentence(strings, connector = ", ")
107
+ to_sentence(strings, connector, ", or ")
108
+ end
109
+
110
+ def humanize(string)
111
+ return nil if string.nil?
112
+
113
+ if string.is_a?(::Symbol)
114
+ string = string.to_s
115
+ end
116
+
117
+ return "" if string.empty?
118
+
119
+ string = string.gsub("_", " ")
120
+ string[0] = string[0].upcase
121
+
122
+ string
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,33 @@
1
+ module Foobara
2
+ module Util
3
+ module_function
4
+
5
+ def deep_stringify_keys(object)
6
+ case object
7
+ when ::Array
8
+ object.map { |element| deep_stringify_keys(element) }
9
+ when ::Hash
10
+ object.to_h do |k, v|
11
+ [k.is_a?(::Symbol) ? k.to_s : k, deep_stringify_keys(v)]
12
+ end
13
+ else
14
+ object
15
+ end
16
+ end
17
+
18
+ def deep_dup(object)
19
+ case object
20
+ when ::Array
21
+ object.map { |element| deep_dup(element) }
22
+ when ::Hash
23
+ object.to_h { |k, v| [deep_dup(k), deep_dup(v)] }
24
+ when ::String
25
+ # Important to not dup ::Module... but going to exclude everything but String for now to prevent that issue
26
+ # and others.
27
+ object.dup
28
+ else
29
+ object
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module Foobara
2
+ module Util
3
+ VERSION = "0.0.4".freeze
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ Dir["#{__dir__}/../**/*.rb"].each { |f| require f }
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foobara-util
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Miles Georgi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-05-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - azimux@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE.txt
21
+ - lib/foobara/truncated_inspect.rb
22
+ - lib/foobara/util.rb
23
+ - lib/foobara/util/args.rb
24
+ - lib/foobara/util/array.rb
25
+ - lib/foobara/util/class.rb
26
+ - lib/foobara/util/hash.rb
27
+ - lib/foobara/util/meta.rb
28
+ - lib/foobara/util/module.rb
29
+ - lib/foobara/util/require.rb
30
+ - lib/foobara/util/string.rb
31
+ - lib/foobara/util/structured.rb
32
+ - lib/foobara/util/version.rb
33
+ homepage: https://github.com/foobara/util
34
+ licenses:
35
+ - Apache-2.0
36
+ - MIT
37
+ metadata:
38
+ homepage_uri: https://github.com/foobara/util
39
+ source_code_uri: https://github.com/foobara/util
40
+ changelog_uri: https://github.com/foobara/util/blob/main/CHANGELOG.md
41
+ rubygems_mfa_required: 'true'
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 3.2.2
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubygems_version: 3.4.10
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Utility functions used across various Foobara projects
61
+ test_files: []