Module: Foobara::Util

Defined in:
foobara-util-0.0.11/lib/foobara/util/args.rb,
foobara-util-0.0.11/lib/foobara/util/hash.rb,
foobara-util-0.0.11/lib/foobara/util/meta.rb,
foobara-util-0.0.11/lib/foobara/util/tree.rb,
foobara-util-0.0.11/lib/foobara/util/array.rb,
foobara-util-0.0.11/lib/foobara/util/class.rb,
foobara-util-0.0.11/lib/foobara/util/module.rb,
foobara-util-0.0.11/lib/foobara/util/string.rb,
foobara-util-0.0.11/lib/foobara/util/require.rb,
foobara-util-0.0.11/lib/foobara/util/structured.rb

Defined Under Namespace

Classes: ParentModuleDoesNotExistError, SubTree, Tree

Constant Summary collapse

IS_CAP =
/[A-Z]/
IS_IDENTIFIER_CHARACTER =
/\w/

Class Method Summary collapse

Class Method Details

.all_blank_or_false?(array) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
35
36
37
38
# File 'foobara-util-0.0.11/lib/foobara/util/array.rb', line 32

def all_blank_or_false?(array)
  array.all? do |element|
    element.nil? || element == false || (
      (element.is_a?(::Hash) || element.is_a?(::Array) || element.is_a?(::String)) && element.empty?
    )
  end
end

.all_symbolic_elements?(array) ⇒ Boolean

Returns:

  • (Boolean)


24
25
26
# File 'foobara-util-0.0.11/lib/foobara/util/array.rb', line 24

def all_symbolic_elements?(array)
  array.all? { |key| key.is_a?(Symbol) || key.is_a?(String) }
end

.all_symbolic_keys?(hash) ⇒ Boolean

Returns:

  • (Boolean)


25
26
27
# File 'foobara-util-0.0.11/lib/foobara/util/hash.rb', line 25

def all_symbolic_keys?(hash)
  all_symbolic_elements?(hash.keys)
end

.all_symbolizable_elements?(array) ⇒ Boolean

Returns:

  • (Boolean)


28
29
30
# File 'foobara-util-0.0.11/lib/foobara/util/array.rb', line 28

def all_symbolizable_elements?(array)
  array.all? { |key| key.is_a?(Symbol) || key.is_a?(String) }
end

.all_symbolizable_keys?(hash) ⇒ Boolean

Returns:

  • (Boolean)


29
30
31
# File 'foobara-util-0.0.11/lib/foobara/util/hash.rb', line 29

def all_symbolizable_keys?(hash)
  all_symbolizable_elements?(hash.keys)
end

.arg_and_opts_to_arg(arg, opts) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'foobara-util-0.0.11/lib/foobara/util/args.rb', line 42

def arg_and_opts_to_arg(arg, opts)
  if arg && !arg.empty?
    if opts && !opts.empty?
      unless opts.is_a?(::Hash)
        # :nocov:
        raise ArgumentError, "opts must be a hash not a #{opts}"
        # :nocov:
      end

      unless arg.is_a?(::Hash)
        # :nocov:
        raise ArgumentError, "arg must be a hash if present when opts is present"
        # :nocov:
      end

      arg.merge(opts)
    else
      arg
    end
  elsif opts && !opts.empty?
    opts
  end
end

.args_and_opts_to_args(args, opts) ⇒ Object

Strange utility method here… idea is there’s a method that takes 0 or 1 arguments which may or may not be a hash. To make sure we don’t act like there’s an argument when there’s not or fail to combine parts of the hash that wind up in opts, this method comines it into an array of 0 or 1 argument with stuff merged into the argument if needed.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'foobara-util-0.0.11/lib/foobara/util/args.rb', line 71

def args_and_opts_to_args(args, opts)
  unless args.is_a?(::Array)
    # :nocov:
    raise ArgumentError, "args must be an array of 0 or 1 hashes but received #{args}"
    # :nocov:
  end

  case args.size
  when 0
    Util.array(arg_and_opts_to_arg(nil, opts))
  when 1
    # Do not go from 1 argument to 0. ie, [nil] should return [nil] not [].
    if opts && !opts.empty?
      arg = args.first

      unless arg.is_a?(::Hash)
        # :nocov:
        raise ArgumentError, "Expected #{arg.inspect} to be a Hash"
        # :nocov:
      end

      [arg_and_opts_to_arg(args.first, opts)]
    else
      args
    end

    [arg_and_opts_to_arg(args.first, opts)]
  else
    # :nocov:
    raise ArgumentError, "args must be an array of 0 or 1 hashes but received #{args}"
    # :nocov:
  end
end

.args_and_opts_to_opts(args, opts) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'foobara-util-0.0.11/lib/foobara/util/args.rb', line 5

def args_and_opts_to_opts(args, opts)
  unless args.is_a?(::Array)
    # :nocov:
    raise ArgumentError, "args must be an array of 0 or 1 hashes but received #{args}"
    # :nocov:
  end

  unless opts.is_a?(::Hash)
    # :nocov:
    raise ArgumentError, "opts must be a hash not a #{opts.class}"
    # :nocov:
  end

  case args.size
  when 0
    opts
  when 1
    arg = args.first

    if opts && !opts.empty?
      unless arg.is_a?(::Hash)
        # :nocov:
        raise ArgumentError, "opts must be a hash not a #{arg.class}"
        # :nocov:
      end

      arg.merge(opts)
    else
      arg
    end
  else
    # :nocov:
    raise ArgumentError, "args must be an array of 0 or 1 hashes but received #{args}"
    # :nocov:
  end
end

.array(object) ⇒ Object



13
14
15
16
17
18
19
20
21
22
# File 'foobara-util-0.0.11/lib/foobara/util/array.rb', line 13

def array(object)
  case object
  when nil
    []
  when Array
    object
  else
    [object]
  end
end

.camelize(string, upcase_first = false) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 9

def camelize(string, upcase_first = false)
  return nil if string.nil?

  if string.is_a?(::Symbol)
    string = string.to_s
  end

  retval = ""

  string.each_char do |char|
    if ["_", "-"].include?(char)
      upcase_first = true
    elsif upcase_first
      retval << char.upcase
      upcase_first = false
    else
      retval << char.downcase
    end
  end

  retval
end

.classify(string) ⇒ Object



5
6
7
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 5

def classify(string)
  camelize(string, true)
end

.const_get_up_hierarchy(mod, name) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'foobara-util-0.0.11/lib/foobara/util/module.rb', line 69

def const_get_up_hierarchy(mod, name)
  mod.const_get(name)
rescue NameError => e
  if mod == Object || e.message !~ /uninitialized constant (.*::)?#{name}\z/
    # :nocov:
    raise
    # :nocov:
  end

  mod = if mod.name&.include?("::")
          module_for(mod)
        else
          Object
        end

  const_get_up_hierarchy(mod, name)
end

.constant_value(mod, constant, inherit: false) ⇒ Object



32
33
34
35
36
# File 'foobara-util-0.0.11/lib/foobara/util/module.rb', line 32

def constant_value(mod, constant, inherit: false)
  if mod.constants(inherit).include?(constant.to_sym)
    mod.const_get(constant, inherit)
  end
end

.constant_values(mod, is_a: nil, extends: nil, inherit: false) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'foobara-util-0.0.11/lib/foobara/util/module.rb', line 38

def constant_values(mod, is_a: nil, extends: nil, inherit: false)
  if inherit && !mod.is_a?(Class)
    # :nocov:
    raise "Cannot pass inherit: true for something that is not a Class"
    # :nocov:
  end

  if inherit
    superklass = mod.superclass
    values = constant_values(mod, is_a:, extends:)
    if superklass == Object
      values
    else
      [
        *values,
        *constant_values(superklass, is_a:, extends:, inherit:)
      ]
    end
  else
    is_a = Util.array(is_a)
    extends = Util.array(extends)

    mod.constants.map { |const| constant_value(mod, const) }.select do |object|
      (is_a.nil? || is_a.empty? || is_a.any? { |klass| object.is_a?(klass) }) &&
        (extends.nil? || extends.empty? || (object.is_a?(Class) && extends.any? do |klass|
          object.ancestors.include?(klass)
        end))
    end.compact
  end
end

.constantify(string) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 35

def constantify(string)
  return nil if string.nil?

  if string.is_a?(::Symbol)
    string = string.to_s
  end

  if string =~ /\A[A-Z_]*\z/
    string.dup
  else
    underscore(string).upcase
  end
end

.constantify_sym(string) ⇒ Object



49
50
51
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 49

def constantify_sym(string)
  constantify(string)&.to_sym
end

.deep_dup(object) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'foobara-util-0.0.11/lib/foobara/util/structured.rb', line 18

def deep_dup(object)
  case object
  when ::Array
    object.map { |element| deep_dup(element) }
  when ::Hash
    object.to_h { |k, v| [deep_dup(k), deep_dup(v)] }
  when ::String
    # Important to not dup ::Module... but going to exclude everything but String for now to prevent that issue
    # and others.
    object.dup
  else
    object
  end
end

.deep_stringify_keys(object) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
# File 'foobara-util-0.0.11/lib/foobara/util/structured.rb', line 5

def deep_stringify_keys(object)
  case object
  when ::Array
    object.map { |element| deep_stringify_keys(element) }
  when ::Hash
    object.to_h do |k, v|
      [k.is_a?(::Symbol) ? k.to_s : k, deep_stringify_keys(v)]
    end
  else
    object
  end
end

.descendants(klass) ⇒ Object



5
6
7
8
9
10
11
12
13
14
# File 'foobara-util-0.0.11/lib/foobara/util/class.rb', line 5

def descendants(klass)
  all = Set.new

  klass.subclasses.each do |subclass|
    all << subclass
    all |= descendants(subclass)
  end

  all
end

.find_constant_through_class_hierarchy(klass, constant) ⇒ Object



33
34
35
36
37
38
39
# File 'foobara-util-0.0.11/lib/foobara/util/class.rb', line 33

def find_constant_through_class_hierarchy(klass, constant)
  if klass.const_defined?(constant)
    klass.const_get(constant)
  else
    find_constant_through_class_hierarchy(klass.superclass, constant)
  end
end

.humanize(string) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 112

def humanize(string)
  return nil if string.nil?

  if string.is_a?(::Symbol)
    string = string.to_s
  end

  return "" if string.empty?

  string = string.gsub("_", " ")
  string[0] = string[0].upcase

  string
end

.instances(klass) ⇒ Object

WARNING: This approach is known as being slow. Probably better for you class to track its own instances if this is being used for smoething more than debugging.



18
19
20
# File 'foobara-util-0.0.11/lib/foobara/util/class.rb', line 18

def instances(klass)
  ObjectSpace.each_object(klass).to_a
end

.kebab_case(string) ⇒ Object



88
89
90
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 88

def kebab_case(string)
  underscore(string)&.gsub("_", "-")
end

.make_class(name, superclass = nil, which: :class, tag: false, &block) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'foobara-util-0.0.11/lib/foobara/util/meta.rb', line 16

def make_class(name, superclass = nil, which: :class, tag: false, &block)
  name = name.to_s if name.is_a?(::Symbol)

  if superclass.is_a?(Class)
    superclass = superclass.name
  end

  inherit = superclass ? " < ::#{superclass}" : ""

  superclass ||= :Object

  name = name[2..] if name.start_with?("::")

  already_exists = Object.const_defined?(name, false)

  if !already_exists && tag
    should_tag = true
  end

  parent_name = parent_module_name_for(name)

  if parent_name && !Object.const_defined?(parent_name, false)
    raise ParentModuleDoesNotExistError.new(name:, parent_name:)
  end

  unless already_exists
    # rubocop:disable Security/Eval, Style/DocumentDynamicEvalDefinition
    eval(<<~RUBY, binding, __FILE__, __LINE__ + 1)
      #{which} ::#{name}#{inherit}
      end
    RUBY
    # rubocop:enable Security/Eval, Style/DocumentDynamicEvalDefinition
  end

  klass = Object.const_get(name, false)

  if should_tag
    klass.instance_variable_set(:@foobara_created_via_make_class, true)
  end

  if block
    if klass.is_a?(::Class)
      klass.class_eval(&block)
    else
      klass.module_eval(&block)
    end
  end

  klass
end

.make_class_p(name, superclass = nil, which: :class, tag: false) ⇒ Object



67
68
69
70
71
72
# File 'foobara-util-0.0.11/lib/foobara/util/meta.rb', line 67

def make_class_p(name, superclass = nil, which: :class, tag: false, &)
  make_class(name, superclass, which:, tag:, &)
rescue ParentModuleDoesNotExistError => e
  make_class_p(e.parent_name, which: :module, tag:, &)
  make_class(name, superclass, which:, tag:, &)
end

.make_module(name) ⇒ Object

TODO: Kind of weird that make_module is implemented in terms of make_class instead of the other way around



75
76
77
# File 'foobara-util-0.0.11/lib/foobara/util/meta.rb', line 75

def make_module(name, &)
  make_class(name, which: :module, &)
end

.make_module_p(name, tag: false) ⇒ Object



79
80
81
# File 'foobara-util-0.0.11/lib/foobara/util/meta.rb', line 79

def make_module_p(name, tag: false, &)
  make_class_p(name, which: :module, tag:, &)
end

.module_for(mod) ⇒ Object



5
6
7
8
9
10
11
12
# File 'foobara-util-0.0.11/lib/foobara/util/module.rb', line 5

def module_for(mod)
  name = mod.name

  return unless name

  name = parent_module_name_for(mod.name)
  Object.const_get(name) if name
end

.non_full_name(mod) ⇒ Object



18
19
20
21
22
23
24
25
26
# File 'foobara-util-0.0.11/lib/foobara/util/module.rb', line 18

def non_full_name(mod)
  name = if mod.is_a?(::String)
           mod
         else
           mod.name
         end

  name&.[](/([^:]+)\z/, 1)
end

.non_full_name_underscore(mod) ⇒ Object



28
29
30
# File 'foobara-util-0.0.11/lib/foobara/util/module.rb', line 28

def non_full_name_underscore(mod)
  underscore(non_full_name(mod))
end

.parent_module_name_for(module_name) ⇒ Object



14
15
16
# File 'foobara-util-0.0.11/lib/foobara/util/module.rb', line 14

def parent_module_name_for(module_name)
  module_name[/(.*)::/, 1]
end

.power_set(array) ⇒ Object



5
6
7
8
9
10
11
# File 'foobara-util-0.0.11/lib/foobara/util/array.rb', line 5

def power_set(array)
  return [[]] if array.empty?

  head, *tail = array
  subsets = power_set(tail)
  subsets + subsets.map { |subset| [head, *subset] }
end


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'foobara-util-0.0.11/lib/foobara/util/tree.rb', line 128

def print_tree(data, io: $stdout, to_name: nil, to_parent: nil)
  data = data.to_a if data.is_a?(::Hash)

  if to_name.nil? && to_parent.nil?
    to_name = :first.to_proc
    to_parent = proc do |object|
      parent_name = object.last
      data.find { |pair| pair.first == parent_name }
    end
  end

  tree = Util::Tree.new(data, to_parent)

  io.puts tree.to_s(to_name)
end

.remove_blank(hash) ⇒ Object



37
38
39
# File 'foobara-util-0.0.11/lib/foobara/util/hash.rb', line 37

def remove_blank(hash)
  remove_empty(hash).compact
end

.remove_constant(const_name) ⇒ Object



87
88
89
90
91
92
# File 'foobara-util-0.0.11/lib/foobara/util/module.rb', line 87

def remove_constant(const_name)
  *path, name = const_name.split("::")
  mod = path.inject(Object) { |m, constant| m.const_get(constant) }

  mod.send(:remove_const, name)
end

.remove_empty(hash) ⇒ Object



33
34
35
# File 'foobara-util-0.0.11/lib/foobara/util/hash.rb', line 33

def remove_empty(hash)
  hash.reject { |_k, v| (v.is_a?(::Hash) || v.is_a?(::Array)) && v.empty? }
end

.require_directory(directory) ⇒ Object



5
6
7
# File 'foobara-util-0.0.11/lib/foobara/util/require.rb', line 5

def require_directory(directory)
  require_pattern("#{directory}/**/*.rb")
end

.require_pattern(glob) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'foobara-util-0.0.11/lib/foobara/util/require.rb', line 9

def require_pattern(glob)
  files = Dir[glob]

  if files.empty?
    # :nocov:
    raise "Didn't find anything to require for #{glob}"
    # :nocov:
  end

  files.sort_by { |file| [file.count("/"), file.length] }.reverse.each do |f|
    require f
  end
end

.super_method_of(current_instance, from_class, method_name) ⇒ Object

Kind of surprising that Ruby doesn’t have a built in way to do this.



23
24
25
26
27
# File 'foobara-util-0.0.11/lib/foobara/util/class.rb', line 23

def super_method_of(current_instance, from_class, method_name)
  method = current_instance.method(method_name)
  method = method.super_method until method.owner == from_class
  method.super_method
end

.super_method_takes_parameters?(current_instance, from_class, method_name) ⇒ Boolean

Returns:

  • (Boolean)


29
30
31
# File 'foobara-util-0.0.11/lib/foobara/util/class.rb', line 29

def super_method_takes_parameters?(current_instance, from_class, method_name)
  super_method_of(current_instance, from_class, method_name).parameters.any?
end

.symbolize_keys(hash) ⇒ Object



5
6
7
8
9
10
11
12
13
# File 'foobara-util-0.0.11/lib/foobara/util/hash.rb', line 5

def symbolize_keys(hash)
  unless all_symbolizable_keys?(hash)
    # :nocov:
    raise "Cannot symbolize keys for #{hash} because they are not all symbolizable"
    # :nocov:
  end

  hash.transform_keys(&:to_sym)
end

.symbolize_keys!(hash) ⇒ Object



15
16
17
18
19
20
21
22
23
# File 'foobara-util-0.0.11/lib/foobara/util/hash.rb', line 15

def symbolize_keys!(hash)
  unless all_symbolizable_keys?(hash)
    # :nocov:
    raise "Cannot symbolize keys for #{hash} because they are not all symbolizable"
    # :nocov:
  end

  hash.transform_keys!(&:to_sym)
end

.to_or_sentence(strings, connector = ", ") ⇒ Object



108
109
110
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 108

def to_or_sentence(strings, connector = ", ")
  to_sentence(strings, connector, ", or ")
end

.to_sentence(strings, connector = ", ", last_connector = ", and ") ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 96

def to_sentence(strings, connector = ", ", last_connector = ", and ")
  return "" if strings.empty?

  *strings, last = strings

  if strings.empty?
    last
  else
    [strings.join(connector), last].join(last_connector)
  end
end

.underscore(string) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 53

def underscore(string)
  return nil if string.nil?

  if string.is_a?(::Symbol)
    string = string.to_s
  end

  return "" if string.empty?

  string = string.gsub(/[-.]/, "_")

  retval = ""
  is_start = true

  string.each_char do |char|
    if IS_IDENTIFIER_CHARACTER =~ char
      if IS_CAP =~ char
        char = char.downcase
        char = "_#{char}" unless is_start
        is_start = false
      elsif char == "_"
        is_start = true
      else
        is_start = false
      end
    else
      is_start = true
    end

    retval << char
  end

  retval
end

.underscore_sym(string) ⇒ Object



92
93
94
# File 'foobara-util-0.0.11/lib/foobara/util/string.rb', line 92

def underscore_sym(string)
  underscore(string)&.to_sym
end