loading
Generated 2026-01-01T21:54:51+00:00

All Files ( 100.0% covered at 4.21 hits/line )

8 files in total.
123 relevant lines, 123 lines covered and 0 lines missed. ( 100.0% )
27 total branches, 27 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/inspectable.rb 100.00 % 25 12 12 0 1.67 100.00 % 0 0 0
lib/inspectable/builder.rb 100.00 % 71 40 40 0 5.55 100.00 % 15 15 0
lib/inspectable/registry.rb 100.00 % 18 8 8 0 6.00 100.00 % 0 0 0
lib/inspectable/sanitizers/data.rb 100.00 % 35 18 18 0 3.56 100.00 % 4 4 0
lib/inspectable/sanitizers/struct.rb 100.00 % 34 17 17 0 3.41 100.00 % 4 4 0
lib/inspectable/sanitizers/type.rb 100.00 % 38 18 18 0 5.00 100.00 % 2 2 0
lib/inspectable/transformers/redactor.rb 100.00 % 12 5 5 0 2.20 100.00 % 2 2 0
lib/inspectable/transformers/typer.rb 100.00 % 12 5 5 0 1.00 100.00 % 0 0 0

lib/inspectable.rb

100.0% lines covered

100.0% branches covered

12 relevant lines. 12 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "inspectable/builder"
  3. 1 require "inspectable/registry"
  4. 1 require "inspectable/sanitizers/data"
  5. 1 require "inspectable/sanitizers/struct"
  6. 1 require "inspectable/sanitizers/type"
  7. 1 require "inspectable/transformers/redactor"
  8. 1 require "inspectable/transformers/typer"
  9. # Main namespace.
  10. 1 module Inspectable
  11. 1 extend Registry
  12. 9 INSPECTOR = -> value { value.inspect }
  13. CONTAINER = {
  14. 1 registry: self,
  15. class: Sanitizers::Type.new,
  16. data: Sanitizers::Data.new,
  17. struct: Sanitizers::Struct.new
  18. }.freeze
  19. 1 def self.[](*, **) = Builder.new(*, **)
  20. end

lib/inspectable/builder.rb

100.0% lines covered

100.0% branches covered

40 relevant lines. 40 lines covered and 0 lines missed.
15 total branches, 15 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Inspectable
  3. # Provides custom object inspection behavior.
  4. 1 class Builder < Module
  5. 1 def initialize *excludes, container: CONTAINER, **transformers
  6. 12 super()
  7. 12 @excludes = excludes
  8. 12 @container = container
  9. 12 @transformers = resolve transformers
  10. 11 validate
  11. 10 define_inspect
  12. 10 freeze
  13. end
  14. 1 def included descendant
  15. 9 descendant.define_singleton_method :method_added do |name|
  16. 6 else: 1 then: 5 return super(name) unless name == :instance_variables_to_inspect
  17. 1 fail NoMethodError, "Defining method :instance_variables_to_inspect is disabled."
  18. end
  19. 9 when: 8 case descendant
  20. 8 else: 1 when Class, Struct, Data then super
  21. 1 else fail TypeError, "Use Class, Struct, or Data."
  22. end
  23. end
  24. 1 private
  25. 1 attr_reader :excludes, :container, :transformers
  26. 1 def resolve transformers
  27. 12 transformers.each.with_object({}) do |(variable, object), collection|
  28. 8 collection[variable] = acquire object
  29. end
  30. end
  31. 1 def acquire object
  32. 8 when: 2 case object
  33. 2 when: 5 when Proc then object
  34. 5 else: 1 when Symbol then container.fetch(:registry).transformers[object]
  35. 1 else fail TypeError, "Invalid transformer: #{object.inspect}. Use Proc or Symbol."
  36. end
  37. end
  38. 1 def validate
  39. 11 else: 1 then: 10 return unless excludes.empty? && transformers.empty?
  40. 1 fail ArgumentError, "Excludes or transformers are required."
  41. end
  42. 1 def define_inspect renderer: method(:render)
  43. 10 define_method :inspect do
  44. 7 klass = self.class
  45. 7 then: 1 else: 6 if klass < Struct then renderer.call self, :struct
  46. 6 then: 1 else: 5 elsif klass < Data then renderer.call self, :data
  47. 5 then: 4 else: 1 elsif klass.is_a? Class then renderer.call self, :class
  48. 1 else renderer.call self, :unknown
  49. end
  50. end
  51. end
  52. 1 def render instance, type
  53. 8 container.fetch(type) { fail TypeError, "Unknown type. Use Class, Struct, or Data." }
  54. 6 .then { |sanitizer| sanitizer.call(instance, *excludes, **transformers) }
  55. end
  56. end
  57. end

lib/inspectable/registry.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Inspectable
  3. # Provides global regsitry for further customization.
  4. 1 module Registry
  5. 1 def self.extended descendant
  6. 7 descendant.add_transformer(:redact, Inspectable::Transformers::Redactor)
  7. .add_transformer(:type, Inspectable::Transformers::Typer)
  8. end
  9. 1 def add_transformer key, function
  10. 18 transformers[key.to_sym] = function
  11. 18 self
  12. end
  13. 1 def transformers = @transformers ||= {}
  14. end
  15. end

lib/inspectable/sanitizers/data.rb

100.0% lines covered

100.0% branches covered

18 relevant lines. 18 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Inspectable
  3. 1 module Sanitizers
  4. # Excludes and transforms data members.
  5. 1 class Data
  6. 1 def initialize pattern: "#<data%<class>s %<body>s>", inspector: INSPECTOR
  7. 6 @pattern = pattern
  8. 6 @inspector = inspector
  9. 6 freeze
  10. end
  11. 1 def call instance, *excludes, **transformers
  12. 5 format pattern,
  13. 5 then: 1 else: 4 class: instance.class.name.then { " #{it}" if it },
  14. body: exclude_and_transform(instance, excludes, transformers).chomp(", ")
  15. end
  16. 1 private
  17. 1 attr_reader :pattern, :inspector
  18. 1 def exclude_and_transform instance, excludes, transformers
  19. 5 (instance.members - excludes).reduce(+"") do |body, member|
  20. 6 value = instance.public_send member
  21. 6 transformer = transformers.fetch member, inspector
  22. 6 else: 5 then: 1 fail ArgumentError, "Invalid transformer registered for: #{member}." unless transformer
  23. 5 body << "#{member}=#{transformer.call value}, "
  24. end
  25. end
  26. end
  27. end
  28. end

lib/inspectable/sanitizers/struct.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Inspectable
  3. 1 module Sanitizers
  4. # Excludes and transforms struct members.
  5. 1 class Struct
  6. 1 def initialize pattern: "#<struct%<class>s %<body>s>", inspector: Inspectable::INSPECTOR
  7. 6 @pattern = pattern
  8. 6 @inspector = inspector
  9. 6 freeze
  10. end
  11. 1 def call instance, *excludes, **transformers
  12. 5 format pattern,
  13. 5 then: 1 else: 4 class: instance.class.name.then { " #{it}" if it },
  14. body: exclude_and_transform(instance, excludes, transformers).chomp(", ")
  15. end
  16. 1 private
  17. 1 attr_reader :pattern, :inspector
  18. 1 def exclude_and_transform instance, excludes, transformers
  19. 5 (instance.members - excludes).reduce(+"") do |body, member|
  20. 6 transformer = transformers.fetch member, inspector
  21. 6 else: 5 then: 1 fail ArgumentError, "Invalid transformer registered for: #{member}." unless transformer
  22. 5 body << "#{member}=#{transformer.call instance[member]}, "
  23. end
  24. end
  25. end
  26. end
  27. end

lib/inspectable/sanitizers/type.rb

100.0% lines covered

100.0% branches covered

18 relevant lines. 18 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Inspectable
  3. 1 module Sanitizers
  4. # Excludes and transforms object types.
  5. 1 class Type
  6. 1 def initialize pattern: "#<%<class>s:%<id>#018x %<body>s>", inspector: INSPECTOR
  7. 5 @pattern = pattern
  8. 5 @inspector = inspector
  9. 5 freeze
  10. end
  11. 1 def call instance, *excludes, **transformers
  12. 10 variables = instance.instance_variables - excludes.map { :"@#{it}" }
  13. 7 format pattern,
  14. class: instance.class,
  15. id: object_id << 1,
  16. body: exclude_and_transform(instance, variables, transformers).chomp(", ")
  17. end
  18. 1 private
  19. 1 attr_reader :pattern, :inspector
  20. 1 def exclude_and_transform instance, variables, transformers
  21. 7 variables.reduce(+"") do |body, variable|
  22. 11 key = variable.to_s.delete_prefix("@").to_sym
  23. 11 transformer = transformers.fetch key, inspector
  24. 11 else: 10 then: 1 fail ArgumentError, "Invalid transformer registered for: #{key}." unless transformer
  25. 10 body << "#{variable}=#{transformer.call instance.instance_variable_get(variable)}, "
  26. end
  27. end
  28. end
  29. end
  30. end

lib/inspectable/transformers/redactor.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Inspectable
  3. 1 module Transformers
  4. # Redacts sensitive information.
  5. 1 module Redactor
  6. 1 module_function
  7. 7 then: 5 else: 1 def call(value) = ("[REDACTED]".inspect if value)
  8. end
  9. end
  10. end

lib/inspectable/transformers/typer.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Inspectable
  3. 1 module Transformers
  4. # Answers object's type.
  5. 1 module Typer
  6. 1 module_function
  7. 1 def call(value) = value.class.name
  8. end
  9. end
  10. end