foobara-util 0.0.4
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/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: []
|