loading
Generated 2025-10-08T23:59:24+00:00

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

8 files in total.
111 relevant lines, 111 lines covered and 0 lines missed. ( 100.0% )
19 total branches, 19 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 % 65 37 37 0 5.24 100.00 % 13 13 0
lib/inspectable/registry.rb 100.00 % 18 8 8 0 6.00 100.00 % 0 0 0
lib/inspectable/sanitizers/classer.rb 100.00 % 36 17 17 0 4.18 100.00 % 0 0 0
lib/inspectable/sanitizers/dater.rb 100.00 % 31 16 16 0 2.81 100.00 % 2 2 0
lib/inspectable/sanitizers/structer.rb 100.00 % 30 15 15 0 2.67 100.00 % 2 2 0
lib/inspectable/transformers/classifier.rb 100.00 % 8 3 3 0 2.00 100.00 % 0 0 0
lib/inspectable/transformers/redactor.rb 100.00 % 8 3 3 0 3.00 100.00 % 2 2 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/classer"
  5. 1 require "inspectable/sanitizers/dater"
  6. 1 require "inspectable/sanitizers/structer"
  7. 1 require "inspectable/transformers/classifier"
  8. 1 require "inspectable/transformers/redactor"
  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::Classer.new,
  16. data: Sanitizers::Dater.new,
  17. struct: Sanitizers::Structer.new
  18. }.freeze
  19. 1 def self.[](*, **) = Builder.new(*, **)
  20. end

lib/inspectable/builder.rb

100.0% lines covered

100.0% branches covered

37 relevant lines. 37 lines covered and 0 lines missed.
13 total branches, 13 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. 11 super()
  7. 11 @excludes = excludes
  8. 11 @container = container
  9. 11 @transformers = resolve transformers
  10. 10 validate
  11. 9 define_inspect
  12. 9 freeze
  13. end
  14. 1 def included descendant
  15. 8 when: 7 case descendant
  16. 7 else: 1 when Class, Struct, Data then super
  17. 1 else fail TypeError, "Use Class, Struct, or Data."
  18. end
  19. end
  20. 1 private
  21. 1 attr_reader :excludes, :container, :transformers
  22. 1 def resolve transformers
  23. 11 transformers.each.with_object({}) do |(variable, object), collection|
  24. 8 collection[variable] = acquire object
  25. end
  26. end
  27. 1 def acquire object
  28. 8 when: 2 case object
  29. 2 when: 5 when Proc then object
  30. 5 else: 1 when Symbol then container.fetch(:registry).transformers[object]
  31. 1 else fail TypeError, "Invalid transformer: #{object.inspect}. Use Proc or Symbol."
  32. end
  33. end
  34. 1 def validate
  35. 10 else: 1 then: 9 return unless excludes.empty? && transformers.empty?
  36. 1 fail ArgumentError, "Excludes or transformers are required."
  37. end
  38. 1 def define_inspect renderer: method(:render)
  39. 9 define_method :inspect do
  40. 7 klass = self.class
  41. 7 then: 1 else: 6 if klass < Struct then renderer.call self, :struct
  42. 6 then: 1 else: 5 elsif klass < Data then renderer.call self, :data
  43. 5 then: 4 else: 1 elsif klass.is_a? Class then renderer.call self, :class
  44. 1 else renderer.call self, :unknown
  45. end
  46. end
  47. end
  48. 1 def render instance, type
  49. 8 container.fetch(type) { fail TypeError, "Unknown type. Use Class, Struct, or Data." }
  50. 6 .then { |serializer| serializer.call(instance, *excludes, **transformers) }
  51. end
  52. end
  53. 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(:class, Inspectable::Transformers::Classifier)
  7. .add_transformer :redact, Inspectable::Transformers::Redactor
  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/classer.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 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 Sanitizers
  4. # Excludes and transforms class instance variables.
  5. 1 class Classer
  6. 1 def initialize pattern: "#<%<class>s:%<id>#018x %<body>s>", inspector: INSPECTOR
  7. 4 @pattern = pattern
  8. 4 @inspector = inspector
  9. 4 freeze
  10. end
  11. 1 def call instance, *excludes, **transformers
  12. 9 variables = instance.instance_variables - excludes.map { :"@#{it}" }
  13. 6 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. 6 variables.reduce(+"") do |body, variable|
  22. 10 key = variable.to_s.delete_prefix("@").to_sym
  23. 10 value = instance.instance_variable_get variable
  24. 10 body << "#{variable}=#{transformers.fetch(key, inspector).call value}, "
  25. end
  26. end
  27. end
  28. end
  29. end

lib/inspectable/sanitizers/dater.rb

100.0% lines covered

100.0% branches covered

16 relevant lines. 16 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 data members.
  5. 1 class Dater
  6. 1 def initialize pattern: "#<data%<class>s %<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. 4 format pattern,
  13. 4 then: 1 else: 3 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. 4 (instance.members - excludes).reduce(+"") do |body, member|
  20. 5 value = instance.public_send member
  21. 5 body << "#{member}=#{transformers.fetch(member, inspector).call value}, "
  22. end
  23. end
  24. end
  25. end
  26. end

lib/inspectable/sanitizers/structer.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 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 struct members.
  5. 1 class Structer
  6. 1 def initialize pattern: "#<struct%<class>s %<body>s>", inspector: Inspectable::INSPECTOR
  7. 5 @pattern = pattern
  8. 5 @inspector = inspector
  9. 5 freeze
  10. end
  11. 1 def call instance, *excludes, **transformers
  12. 4 format pattern,
  13. 4 then: 1 else: 3 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. 4 (instance.members - excludes).reduce(+"") do |body, member|
  20. 5 body << "#{member}=#{transformers.fetch(member, inspector).call instance[member]}, "
  21. end
  22. end
  23. end
  24. end
  25. end

lib/inspectable/transformers/classifier.rb

100.0% lines covered

100.0% branches covered

3 relevant lines. 3 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 value's class.
  5. 4 Classifier = -> value { value.class.name }
  6. end
  7. end

lib/inspectable/transformers/redactor.rb

100.0% lines covered

100.0% branches covered

3 relevant lines. 3 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. 7 then: 5 else: 1 Redactor = -> value { "[REDACTED]".inspect if value }
  6. end
  7. end