module Eco module Language def to_key(pr) pr.is_a?(Hash) ? pr.keys.first : pr end def to_value(pr) pr.is_a?(Hash) ? pr.values.first : pr end def values_at(object, *attrs, **kattrs) all_params = attrs + kattrs.map {|k,v| {k => v} } return object.values_at(*all_params) if object.is_a?(Hash) all_keys = all_params.map { |pr| to_key(pr) } arguments = all_params.each_with_index.map { |pr, i| pr.values[0] if pr.is_a?(Hash)} #puts "all_keys: #{all_keys}; arguments: #{arguments}" values = all_keys.each_with_index.map do |k, i| a = k; v = [] v = arguments[i] if all_params[i].is_a?(Hash) # with params attr_method = get_accessor(object, a)[object, a] rescue nil #puts "attr: #{a} - method: #{attr_method}" next if !attr_method args = v.is_a?(Array) ? v : [].push(v) attr_method = curry(attr_method) if required_parameters?(attr_method) # final call to the method #puts "args: #{args}" value = attr_method[*args] # rescue nil value = nil if value.is_a?(Proc) value end end # adapted from: https://stackoverflow.com/a/16908153/4352306 def get_accessor(object, attr, accessors: []) accessors.push( ->(obj, att) { object.method(att) } ) #accessors.push( ->(obj, att) { object[att] } ) accessors.reduce(nil) { |chosen, acc| chosen ? chosen : (acc[object, attr] && acc rescue nil) || chosen } #accessors.reduce(nil) { |chosen, acc| # puts "attr: #{attr} => current: #{acc}" # chosen ? chosen : (acc[object, attr] && acc rescue nil) || chosen # puts "attr: #{attr} => after check - chosen: #{chosen}" # chosen # } end # enables dot notation to do object nesting access def values_at_dot (object, *attrs, curry: false, debug: false, **kattrs) all_params = attrs + kattrs.map {|k,v| {k => v} } return object.values_at(*all_params) if object.is_a?(Hash) to_s = ->(k) { ((k.to_s rescue k) || key) } to_sym = ->(k) { ((k.to_sym rescue k) || key) } dotted = ->(k) { to_s[k].include?('.') } pdotted = ->(pr) { dotted[to_key(pr)] } haccess = ->(dp) { # prepare call k = dp k, v = [dp.keys.first, dp.values.first] if (hash = dp.is_a?(Hash)) kf, kr = [ (ss = to_s[k].split('.')).first , ss.slice(1)] nxt_cll = hash ? {kr => v} : kr {k => {'a' => kf, 'v' => nxt_cll }} } all_keys = all_params.map { |pr| to_key(pr) } hash_params = all_params.reduce({}) { |h,pr| h.merge!(pr) if pr.is_a?(Hash); h } dot_params = all_params.select { |pr| pdotted[pr] } dot_keys = dot_params.map { |dp| to_key(dp) } dot_wrappers = dot_params.reduce({}) { |h,dp| h.merge(haccess[dp]) } if debug pp "object : #{object}" pp "all_params: #{all_params}" pp "dot_keys : #{dot_keys}" end values = all_keys.map do |k| a = k; v = [] puts "key: #{k}" if debug case true when dot_keys.include?(k) # dotted (recursive) dot = dot_wrappers[k] a, v = dot.values_at('a', 'v') when hash_params.key?(k) # no dotted, with params v = hash_params[k] end attr_method = get_accessor(object, a)[object, a] rescue nil next if !attr_method args = v.is_a?(Array) ? v : [].push(v) attr_method = curry(attr_method) if required_parameters?(attr_method) if dot_keys.include?(k) # recurse (dotted) args.push({ curry: curry, debug: debug }) value = values_at(attr_method.call, *args).first else # final call to the method value = attr_method[*args] # rescue nil puts "value: #{value}" if debug end value = nil if !curry && value.is_a?(Proc) value end end module Test class A attr_reader :ops, :cops def initialize(val) @ops = val end def mops 'm-' + @ops end def cops=(value) @ops = value end def rops(val) @ops + " there we go with '#{val}' !!" end def xops(val, xop:) @ops + " #{val}!! Xops do the trick with '#{xop}'." end private def gops 'g+' + @ops end end class B attr_accessor :a, :c def initialize(a) @a = A.new(a) end def c=(value) @c = value end def bofs(v1, v2:) "#{v1} #{v2} with #{@c}" end end end def self.test_values_at h = {'pops' => 'aghr!', pops: 5, 'nops' => 'nope!'} a = Test::A.new('hello!') b = Test::B.new('bye') b.c = 'cheese' pp Handy.values_at(h, 'nops', :pops) pp Handy.values_at(a, 'rops', 'mops', rops: 'EVERY', xops: ['Dear', xop: 'SOMETHING BETTER']) pp Handy.values_at(a, 'mops', 'gops', rops: 'EVERY THING') pp Handy.values_at_dot(b, 'c', 'bofs' => ['stranberry', v2: 'combines'], 'a.rops': 'DOTTED CALL') pp "+" * 60 pp Handy.values_at_dot(b, 'c', 'bofs' => ['stranberry', v2: 'combines'], debug: true, 'a.rops': 'DOTTED CALL') pp "+" * 60 end end end