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

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

16 files in total.
235 relevant lines, 235 lines covered and 0 lines missed. ( 100.0% )
17 total branches, 17 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/irb/kit.rb 100.00 % 26 14 14 0 1.14 100.00 % 0 0 0
lib/irb/kit/commands/descendants.rb 100.00 % 37 14 14 0 1.00 100.00 % 0 0 0
lib/irb/kit/handlers/abstract.rb 100.00 % 23 10 10 0 3.80 100.00 % 0 0 0
lib/irb/kit/handlers/clipper.rb 100.00 % 26 13 13 0 1.69 100.00 % 0 0 0
lib/irb/kit/handlers/constant_editor.rb 100.00 % 25 12 12 0 1.42 100.00 % 0 0 0
lib/irb/kit/handlers/descender.rb 100.00 % 36 18 18 0 351.44 100.00 % 2 2 0
lib/irb/kit/handlers/editor.rb 100.00 % 32 17 17 0 2.47 100.00 % 2 2 0
lib/irb/kit/handlers/method_editor.rb 100.00 % 25 12 12 0 1.42 100.00 % 0 0 0
lib/irb/kit/handlers/paster.rb 100.00 % 25 12 12 0 1.42 100.00 % 0 0 0
lib/irb/kit/handlers/searcher.rb 100.00 % 28 13 13 0 2.00 100.00 % 2 2 0
lib/irb/kit/helpers/clip.rb 100.00 % 30 14 14 0 1.14 100.00 % 0 0 0
lib/irb/kit/helpers/edit_source.rb 100.00 % 35 18 18 0 1.33 100.00 % 3 3 0
lib/irb/kit/helpers/paste.rb 100.00 % 25 12 12 0 1.00 100.00 % 0 0 0
lib/irb/kit/helpers/search.rb 100.00 % 25 12 12 0 1.00 100.00 % 0 0 0
lib/irb/kit/prompter.rb 100.00 % 49 27 27 0 3.22 100.00 % 6 6 0
lib/irb/kit/register.rb 100.00 % 32 17 17 0 5.53 100.00 % 2 2 0

lib/irb/kit.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "irb"
  3. 1 require "zeitwerk"
  4. 1 Zeitwerk::Loader.new.then do |loader|
  5. 1 loader.inflector.inflect "irb" => "IRB"
  6. 1 loader.tag = "irb-kit"
  7. 1 loader.push_dir "#{__dir__}/.."
  8. 1 loader.setup
  9. end
  10. 1 module IRB
  11. # Main namespace.
  12. 1 module Kit
  13. 1 def self.loader registry = Zeitwerk::Registry
  14. 3 @loader ||= registry.loaders.each.find { |loader| loader.tag == "irb-kit" }
  15. end
  16. 1 def self.register_commands(*) = Register.new(IRB::Command, :Commands).call(*)
  17. 1 def self.register_helpers(*) = Register.new(IRB::HelperMethod, :Helpers).call(*)
  18. 1 def self.prompt = @prompt ||= Prompter.new.call
  19. end
  20. end

lib/irb/kit/commands/descendants.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module IRB
  3. 1 module Kit
  4. 1 module Commands
  5. # Answers descendants of a class.
  6. 1 class Descendants < IRB::Command::Base
  7. 1 MONIKER = :descendants
  8. 1 category "Kit"
  9. 1 description "Show class descendants."
  10. 1 help_message <<~BODY
  11. Usage: descendants <class>
  12. Examples:
  13. descendants Object
  14. descendants IO
  15. descendants Ractor::Error
  16. BODY
  17. 1 def initialize context, handler: Handlers::Descender.new
  18. 1 super context
  19. 1 @handler = handler
  20. end
  21. 1 def execute(name) = handler.call(name)
  22. 1 private
  23. 1 attr_reader :handler
  24. end
  25. end
  26. end
  27. end

lib/irb/kit/handlers/abstract.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module IRB
  3. 1 module Kit
  4. 1 module Handlers
  5. # Provides default functionality for all handlers.
  6. 1 class Abstract
  7. 1 def initialize io: STDOUT
  8. 29 @io = io
  9. end
  10. 1 def call
  11. 1 fail NoMethodError,
  12. "`#{self.class}##{__method__} #{method(__method__).parameters}` must be implemented."
  13. end
  14. 1 protected
  15. 1 attr_reader :io
  16. end
  17. end
  18. end
  19. end

lib/irb/kit/handlers/clipper.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module IRB
  3. 1 module Kit
  4. 1 module Handlers
  5. # Handles copying content to the macOS clipboard.
  6. 1 class Clipper < Abstract
  7. 1 def initialize(processor: IO, **)
  8. 4 @processor = processor
  9. 4 super(**)
  10. end
  11. 1 def call(*lines)
  12. 3 processor.popen("pbcopy", "w") { |clipboard| clipboard.write lines.join("\n") }
  13. 2 io.puts "Copied to clipboard."
  14. rescue Errno::ENOENT
  15. 1 io.puts "ERROR: Unable to copy since `pbcopy` is only supported on macOS."
  16. end
  17. 1 private
  18. 1 attr_reader :processor
  19. end
  20. end
  21. end
  22. end

lib/irb/kit/handlers/constant_editor.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 module IRB
  3. 1 module Kit
  4. 1 module Handlers
  5. # Handles editing of constant source code.
  6. 1 class ConstantEditor < Abstract
  7. 1 def initialize(editor: Editor.new, **)
  8. 3 @editor = editor
  9. 3 super(**)
  10. end
  11. 1 def call name
  12. 2 editor.call(*Object.const_source_location(name))
  13. rescue NameError
  14. 1 io.puts "ERROR (invalid constant): #{name.inspect}."
  15. end
  16. 1 private
  17. 1 attr_reader :editor
  18. end
  19. end
  20. end
  21. end

lib/irb/kit/handlers/descender.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 IRB
  3. 1 module Kit
  4. 1 module Handlers
  5. # Handles finding the descendants of a class.
  6. 1 class Descender < Abstract
  7. 1 def initialize(collector: ObjectSpace, **)
  8. 4 @collector = collector
  9. 4 super(**)
  10. end
  11. 1 def call name
  12. 7 then: 1 else: 2 collect(name).then { |all| all.empty? ? "No descendants found." : all.join("\n") }
  13. 3 .then { |result| io.puts result }
  14. rescue NameError
  15. 1 io.puts "ERROR: #{name.inspect} doesn't exist."
  16. end
  17. 1 private
  18. 1 attr_reader :collector
  19. 1 def collect name
  20. 4 superclass = Object.const_get name
  21. 3 collector.each_object(Class)
  22. 6288 .select { |subclass| subclass < superclass }
  23. 3 .tap { |descendants| descendants.delete superclass }
  24. .map(&:to_s)
  25. .sort
  26. end
  27. end
  28. end
  29. end
  30. end

lib/irb/kit/handlers/editor.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module IRB
  3. 1 module Kit
  4. 1 module Handlers
  5. # Handles editing of source code.
  6. 1 class Editor < Abstract
  7. 1 def initialize(environment: ENV, kernel: Kernel, **)
  8. 6 @environment = environment
  9. 6 @kernel = kernel
  10. 6 super(**)
  11. end
  12. 1 def call path, line = nil
  13. 4 path_and_line = [path, line].join ":"
  14. 4 then: 3 if path && Pathname(path).exist?
  15. 3 io.puts "Editing: #{path_and_line}..."
  16. 3 kernel.system %(#{environment.fetch "EDITOR"} #{path_and_line})
  17. else: 1 else
  18. 1 io.puts "ERROR (invalid path): #{path}."
  19. end
  20. 1 rescue KeyError then io.puts "ERROR: The `EDITOR` environment variable must be defined."
  21. end
  22. 1 private
  23. 1 attr_reader :environment, :kernel
  24. end
  25. end
  26. end
  27. end

lib/irb/kit/handlers/method_editor.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 module IRB
  3. 1 module Kit
  4. 1 module Handlers
  5. # Handles editing of method source code.
  6. 1 class MethodEditor < Abstract
  7. 1 def initialize(editor: Editor.new, **)
  8. 3 @editor = editor
  9. 3 super(**)
  10. end
  11. 1 def call object, name
  12. 2 editor.call(*object.method(name).source_location)
  13. rescue NameError
  14. 1 io.puts "ERROR: Undefined method `#{name.inspect}` for `#{object.inspect}`."
  15. end
  16. 1 private
  17. 1 attr_reader :editor
  18. end
  19. end
  20. end
  21. end

lib/irb/kit/handlers/paster.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 module IRB
  3. 1 module Kit
  4. 1 module Handlers
  5. # Handles pasting content from the macOS clipboard.
  6. 1 class Paster < Abstract
  7. 1 def initialize(processor: IO, **)
  8. 3 @processor = processor
  9. 3 super(**)
  10. end
  11. 1 def call
  12. 2 processor.popen "pbpaste", "r", &:read
  13. rescue Errno::ENOENT
  14. 1 io.puts "ERROR: Unable to paste since `pbpaste` is only supported on macOS."
  15. end
  16. 1 private
  17. 1 attr_reader :processor
  18. end
  19. end
  20. end
  21. end

lib/irb/kit/handlers/searcher.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module IRB
  3. 1 module Kit
  4. 1 module Handlers
  5. # Handles searching an object's methods by pattern.
  6. 1 class Searcher < Abstract
  7. 1 def initialize(matcher: Regexp, **)
  8. 5 @matcher = matcher
  9. 5 super(**)
  10. end
  11. 1 def call object, pattern
  12. 4 object.methods
  13. .grep(matcher.new(pattern))
  14. .join("\n")
  15. 3 then: 1 else: 2 .then { |result| result.empty? ? io.puts("No matches found.") : io.puts(result) }
  16. rescue TypeError
  17. 1 io.puts "ERROR: Use only a string or regular expression for the pattern."
  18. end
  19. 1 private
  20. 1 attr_reader :matcher
  21. end
  22. end
  23. end
  24. end

lib/irb/kit/helpers/clip.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module IRB
  3. 1 module Kit
  4. 1 module Helpers
  5. # Copies input to macOS clipboard.
  6. 1 class Clip < IRB::HelperMethod::Base
  7. 1 MONIKER = :clip
  8. 1 description "Copy input to macOS clipboard. DEPRECATED."
  9. 1 def initialize handler: Handlers::Clipper.new
  10. 1 super()
  11. 1 @handler = handler
  12. end
  13. 1 def execute(*)
  14. 2 warn "`clip` is deprecated, use IRB's native `copy` helper instead.",
  15. category: :deprecated
  16. 2 handler.call(*)
  17. end
  18. 1 private
  19. 1 attr_reader :handler
  20. end
  21. end
  22. end
  23. end

lib/irb/kit/helpers/edit_source.rb

100.0% lines covered

100.0% branches covered

18 relevant lines. 18 lines covered and 0 lines missed.
3 total branches, 3 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "pathname"
  3. 1 module IRB
  4. 1 module Kit
  5. 1 module Helpers
  6. # Edits the source code of a constant or method.
  7. 1 class EditSource < IRB::HelperMethod::Base
  8. 1 MONIKER = :esource
  9. 1 description "Edit the source code of a constant or method in your default editor."
  10. 1 def initialize constant_handler: Handlers::ConstantEditor.new,
  11. method_handler: Handlers::MethodEditor.new
  12. 1 super()
  13. 1 @constant_handler = constant_handler
  14. 1 @method_handler = method_handler
  15. end
  16. 1 def execute(*arguments)
  17. 5 in: 2 case arguments
  18. 2 in: 2 in [name] then puts constant_handler.call(name)
  19. 2 else: 1 in [object, name] then puts method_handler.call(object, name)
  20. 1 else puts "ERROR: Invalid constant or method for arguments: #{arguments.inspect}."
  21. end
  22. end
  23. 1 private
  24. 1 attr_reader :constant_handler, :method_handler
  25. end
  26. end
  27. end
  28. end

lib/irb/kit/helpers/paste.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 module IRB
  3. 1 module Kit
  4. 1 module Helpers
  5. # Pastes last entry from macOS clipboard.
  6. 1 class Paste < IRB::HelperMethod::Base
  7. 1 MONIKER = :paste
  8. 1 description "Paste last entry from macOS clipboard."
  9. 1 def initialize handler: Handlers::Paster.new
  10. 1 super()
  11. 1 @handler = handler
  12. end
  13. 1 def execute = handler.call
  14. 1 private
  15. 1 attr_reader :handler
  16. end
  17. end
  18. end
  19. end

lib/irb/kit/helpers/search.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 module IRB
  3. 1 module Kit
  4. 1 module Helpers
  5. # Search an object's methods by pattern.
  6. 1 class Search < IRB::HelperMethod::Base
  7. 1 MONIKER = :search
  8. 1 description "Search an object's methods by pattern."
  9. 1 def initialize handler: Handlers::Searcher.new
  10. 1 super()
  11. 1 @handler = handler
  12. end
  13. 1 def execute(*) = handler.call(*)
  14. 1 private
  15. 1 attr_reader :handler
  16. end
  17. end
  18. end
  19. end

lib/irb/kit/prompter.rb

100.0% lines covered

100.0% branches covered

27 relevant lines. 27 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module IRB
  3. 1 module Kit
  4. # Dynamically computes prompt based environment.
  5. 1 class Prompter
  6. 1 COLORS = {green: 32, red: 31}.freeze
  7. 1 def initialize version = RUBY_VERSION, delimiter: "|", colors: COLORS
  8. 8 super()
  9. 8 @version = version
  10. 8 @delimiter = delimiter
  11. 8 @colors = colors
  12. end
  13. 1 def call
  14. 8 then: 2 if defined? Hanami::VERSION
  15. 2 else: 6 details Hanami::VERSION, Hanami.app.name.delete_suffix("::App"), Hanami.env
  16. 6 then: 2 elsif defined? Rails
  17. 2 else: 4 details Rails.version, Rails.application.class.module_parent_name, Rails.env
  18. 4 else version_with_optional_project
  19. end
  20. end
  21. 1 private
  22. 1 attr_reader :version, :delimiter, :colors
  23. 1 def details framework_version, application_name, environment
  24. 4 [version, framework_version, application_name.downcase, color(environment)].join delimiter
  25. end
  26. 1 def color environment
  27. 4 then: 2 else: 2 code = environment.to_sym == :production ? red : green
  28. 4 "\e[#{code}m#{environment}\e[0m"
  29. end
  30. 1 def version_with_optional_project
  31. 4 File.basename(`git rev-parse --show-toplevel 2> /dev/null`.strip)
  32. .downcase
  33. 4 .then { |project| [version, project].reject(&:empty?).join delimiter }
  34. end
  35. 1 def green = colors.fetch __method__
  36. 1 def red = colors.fetch __method__
  37. end
  38. end
  39. end

lib/irb/kit/register.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module IRB
  3. 1 module Kit
  4. # Loads extensions for namespace.
  5. 1 class Register
  6. 1 ALL = [:all].freeze
  7. 1 def initialize registrar, namespace, all: ALL
  8. 7 @registrar = registrar
  9. 7 @namespace = IRB::Kit.const_get namespace
  10. 7 @all = all
  11. end
  12. 8 then: 3 else: 4 def call(*monikers) = monikers == all ? maximum : only(*monikers)
  13. 1 private
  14. 1 attr_reader :registrar, :namespace, :all
  15. 10 def maximum = constants.each { |helper| registrar.register helper::MONIKER, helper }
  16. 1 def only(*monikers)
  17. 17 constants.select { |helper| monikers.include? helper::MONIKER }
  18. 4 .then { |selected| monikers.zip selected }
  19. 3 .each { |moniker, helper| registrar.register moniker, helper }
  20. end
  21. 23 def constants = namespace.constants.sort.map { |name| namespace.const_get name }
  22. end
  23. end
  24. end