foobara-util 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +8 -0
- data/lib/foobara/truncated_inspect.rb +65 -0
- data/lib/foobara/util/args.rb +105 -0
- data/lib/foobara/util/array.rb +40 -0
- data/lib/foobara/util/class.rb +33 -0
- data/lib/foobara/util/hash.rb +41 -0
- data/lib/foobara/util/meta.rb +83 -0
- data/lib/foobara/util/module.rb +94 -0
- data/lib/foobara/util/require.rb +23 -0
- data/lib/foobara/util/string.rb +125 -0
- data/lib/foobara/util/structured.rb +33 -0
- data/lib/foobara/util/version.rb +5 -0
- data/lib/foobara/util.rb +1 -0
- metadata +61 -0
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
|
data/lib/foobara/util.rb
ADDED
@@ -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: []
|