diff --git a/.gitignore b/.gitignore index d85211a..048755a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ /spec/reports/ /tmp/ spec/examples.txt - +.ruby-version +.ruby-gemset diff --git a/lib/dry/core/inspect.rb b/lib/dry/core/inspect.rb new file mode 100644 index 0000000..78b9424 --- /dev/null +++ b/lib/dry/core/inspect.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Dry + module Core + class Inspect < ::Module + def initialize(*attributes, **value_methods_map) + super() + + define_inspect_methods(attributes.flatten(1), **value_methods_map) + end + + private + + def define_inspect_methods(attributes, **value_methods_map) + define_inspect_method(attributes, **value_methods_map) + define_pretty_print(attributes, **value_methods_map) + end + + def define_inspect_method(attributes, **value_methods_map) + define_method(:inspect) do + instance_inspect = if self.class.name + "#<#{self.class.name}" + else + Kernel.instance_method(:to_s).bind(self).call.chomp!(">") + end + + inspect = attributes.map { |name| + "#{name}=#{__send__(value_methods_map[name] || name).inspect}" + }.join(", ") + "#{instance_inspect} #{inspect}>" + end + end + + def define_pretty_print(attributes, **value_methods_map) + define_method(:pretty_print) do |pp| + object_group_method = self.class.name ? :object_group : :object_address_group + pp.public_send(object_group_method, self) do + pp.seplist(attributes, -> { pp.text "," }) do |name| + pp.breakable " " + pp.group(1) do + pp.text name.to_s + pp.text "=" + pp.pp __send__(value_methods_map[name] || name) + end + end + end + end + end + end + end +end diff --git a/spec/dry/core/inspect_spec.rb b/spec/dry/core/inspect_spec.rb new file mode 100644 index 0000000..7376899 --- /dev/null +++ b/spec/dry/core/inspect_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "pp" + +RSpec.describe Dry::Core::Inspect do + subject(:inspect) { Dry::Core::Inspect.new(:fullname, :secret_question_answer, **value_methods_map) } + + let(:value_methods_map) { {} } + let(:klass) do + Class.new do + attr_reader :fullname, :secret_question_answer + private :fullname, :secret_question_answer + + def initialize(fullname, secret_question_answer) + @fullname = fullname + @secret_question_answer = secret_question_answer + end + + private + + def secret_question_answer_encrypted + @secret_question_answer * 42 + end + end + end + let(:instance) { klass.new("John Doe", 42) } + + before { klass.include(inspect) } + + it "defines a pretty_print method" do + expect(instance.pretty_inspect) + .to match(/\A#<#:0x[a-f0-9]{16}\n fullname="John Doe",\n secret_question_answer=42>\n\z/) + end + + it "defines an inspect method" do + expect(instance.inspect) + .to match(/\A#<#:0x[a-f0-9]{16} fullname="John Doe", secret_question_answer=42>\z/) + end + + context "with a non-anonymous class" do + before { stub_const("User", klass) } + + it "uses the class name in the pretty_inspect output" do + expect(instance.pretty_inspect).to eq("#\n") + end + + it "uses the class name in the inspect output" do + expect(instance.inspect).to eq("#") + end + end + + context "with a value method given" do + let(:value_methods_map) { {secret_question_answer: :secret_question_answer_encrypted} } + + it "uses the value method in the pretty_inspect output" do + expect(instance.pretty_inspect) + .to match(/\A#<#:0x[a-f0-9]{16}\n fullname="John Doe",\n secret_question_answer=1764>\n\z/) + end + + it "uses the value method in the inspect output" do + expect(instance.inspect) + .to match(/\A#<#:0x[a-f0-9]{16} fullname="John Doe", secret_question_answer=1764>\z/) + end + end +end