|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require_relative 'limited_pp' |
| 4 | + |
| 5 | +module DEBUGGER__ |
| 6 | + class VariableInspector |
| 7 | + class Member |
| 8 | + attr_reader :name, :value |
| 9 | + |
| 10 | + def initialize(name:, value:, internal: false) |
| 11 | + @name = name |
| 12 | + @value = value |
| 13 | + @is_internal = internal |
| 14 | + end |
| 15 | + |
| 16 | + def internal? |
| 17 | + @is_internal |
| 18 | + end |
| 19 | + |
| 20 | + def self.internal name:, value: |
| 21 | + new(name:, value:, internal: true) |
| 22 | + end |
| 23 | + |
| 24 | + def ==(other) |
| 25 | + other.instance_of?(self.class) && |
| 26 | + @name == other.name && |
| 27 | + @value == other.value && |
| 28 | + @is_internal == other.internal? |
| 29 | + end |
| 30 | + |
| 31 | + def inspect |
| 32 | + "#<Member name=#{@name.inspect} value=#{@value.inspect}#{@is_internal ? " internal" : ""}>" |
| 33 | + end |
| 34 | + end |
| 35 | + |
| 36 | + def indexed_members_of obj, start:, count: |
| 37 | + return [] if start > (obj.length - 1) |
| 38 | + |
| 39 | + capped_count = [count, obj.length - start].min |
| 40 | + |
| 41 | + (start...(start + capped_count)).map do |i| |
| 42 | + Member.new(name: i.to_s, value: obj[i]) |
| 43 | + end |
| 44 | + end |
| 45 | + |
| 46 | + def named_members_of obj |
| 47 | + members = case obj |
| 48 | + when Hash then obj.map { |k, v| Member.new(name: value_inspect(k), value: v) } |
| 49 | + when Struct then obj.members.map { |name| Member.new(name:, value: obj[name]) } |
| 50 | + when String |
| 51 | + members = [ |
| 52 | + Member.internal(name: '#length', value: obj.length), |
| 53 | + Member.internal(name: '#encoding', value: obj.encoding), |
| 54 | + ] |
| 55 | + |
| 56 | + printed_str = value_inspect(obj) |
| 57 | + members << Member.internal(name: "#dump", value: NaiveString.new(obj)) if printed_str.end_with?('...') |
| 58 | + |
| 59 | + members |
| 60 | + when Class, Module then [Member.internal(name: "%ancestors", value: obj.ancestors[1..])] |
| 61 | + when Range then [ |
| 62 | + Member.internal(name: "#begin", value: obj.begin), |
| 63 | + Member.internal(name: "#end", value: obj.end), |
| 64 | + ] |
| 65 | + else [] |
| 66 | + end |
| 67 | + |
| 68 | + unless NaiveString === obj |
| 69 | + members += M_INSTANCE_VARIABLES.bind_call(obj).sort.map{|iv| |
| 70 | + Member.new(name: iv, value: M_INSTANCE_VARIABLE_GET.bind_call(obj, iv)) |
| 71 | + } |
| 72 | + members.unshift Member.internal(name: '#class', value: M_CLASS.bind_call(obj)) |
| 73 | + end |
| 74 | + |
| 75 | + members |
| 76 | + end |
| 77 | + |
| 78 | + private |
| 79 | + |
| 80 | + MAX_LENGTH = 180 |
| 81 | + |
| 82 | + def value_inspect obj, short: true |
| 83 | + # TODO: max length should be configurable? |
| 84 | + str = LimitedPP.safe_inspect obj, short: short, max_length: MAX_LENGTH |
| 85 | + |
| 86 | + if str.encoding == Encoding::UTF_8 |
| 87 | + str.scrub |
| 88 | + else |
| 89 | + str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) |
| 90 | + end |
| 91 | + end |
| 92 | + |
| 93 | + # TODO: Replace with Reflection helpers once they are merged |
| 94 | + # https://github.com/ruby/debug/pull/1002 |
| 95 | + M_INSTANCE_VARIABLES = method(:instance_variables).unbind |
| 96 | + M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind |
| 97 | + M_CLASS = method(:class).unbind |
| 98 | + |
| 99 | + class NaiveString |
| 100 | + attr_reader :str |
| 101 | + def initialize str |
| 102 | + @str = str |
| 103 | + end |
| 104 | + |
| 105 | + def == other |
| 106 | + other.instance_of?(self.class) && @str == other.str |
| 107 | + end |
| 108 | + end |
| 109 | + end |
| 110 | +end |
0 commit comments