Skip to content

Commit

Permalink
Add IRB::HelperMethod to support helper methods
Browse files Browse the repository at this point in the history
  • Loading branch information
st0012 committed Apr 17, 2024
1 parent 8886434 commit 29f8ed7
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 24 deletions.
16 changes: 0 additions & 16 deletions lib/irb/command/context.rb

This file was deleted.

2 changes: 2 additions & 0 deletions lib/irb/command/help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def execute(command_name)

def help_message
commands_info = IRB::ExtendCommandBundle.all_commands_info
helper_methods_info = IRB::HelperMethod.all_helper_methods_info
commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] }
commands_grouped_by_categories["Helper methods"] = helper_methods_info

user_aliases = irb_context.instance_variable_get(:@user_aliases)

Expand Down
19 changes: 11 additions & 8 deletions lib/irb/default_commands.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require_relative "command"
require_relative "command/context"
require_relative "helper_method"
require_relative "command/exit"
require_relative "command/force_exit"
require_relative "command/chws"
Expand Down Expand Up @@ -40,13 +40,6 @@ module ExtendCommandBundle
# See #install_alias_method.
OVERRIDE_ALL = 0x02

Command._register_with_aliases(:irb_context, Command::Context,
[
[:context, NO_OVERRIDE],
[:conf, NO_OVERRIDE],
],
)

Command._register_with_aliases(:irb_exit, Command::Exit,
[:exit, OVERRIDE_PRIVATE_ONLY],
[:quit, OVERRIDE_PRIVATE_ONLY],
Expand Down Expand Up @@ -236,6 +229,16 @@ def self.load_command(command)
nil
end

def self.install_helper_methods
HelperMethod.helper_methods.each do |name, helper_method_class|
define_method name do |*args, **opts, &block|
helper_method_class.new.execute(*args, **opts, &block)
end unless method_defined?(name)
end
end

install_helper_methods

# Deprecated. Doesn't have any effect.
@EXTEND_COMMANDS = []

Expand Down
33 changes: 33 additions & 0 deletions lib/irb/helper_method.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require_relative "helper_method/base"

module IRB
module HelperMethod
@helper_methods = {}

class << self
attr_reader :helper_methods

def register(name, helper_class)
@helper_methods[name] = helper_class

if defined?(IRB::ExtendCommandBundle)
IRB::ExtendCommandBundle.install_helper_methods
end
end

def all_helper_methods_info
@helper_methods.map do |name, helper_class|
{ display_name: name, description: helper_class.description }
end
end
end

# Default helper_methods
require_relative "helper_method/conf"
require_relative "helper_method/context"
require_relative "helper_method/irb_context"
register(:conf, HelperMethod::Conf)
register(:context, HelperMethod::Context)
register(:irb_context, HelperMethod::IrbContext)
end
end
12 changes: 12 additions & 0 deletions lib/irb/helper_method/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module IRB
module HelperMethod
class Base
class << self
def description(description = nil)
@description = description if description
@description
end
end
end
end
end
11 changes: 11 additions & 0 deletions lib/irb/helper_method/conf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module IRB
module HelperMethod
class Conf < Base
description "Returns the current context."

def execute
IRB.CurrentContext
end
end
end
end
9 changes: 9 additions & 0 deletions lib/irb/helper_method/context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require_relative "conf"

module IRB
module HelperMethod
class Context < Conf
description "Returns the current context."
end
end
end
9 changes: 9 additions & 0 deletions lib/irb/helper_method/irb_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require_relative "conf"

module IRB
module HelperMethod
class IrbContext < Conf
description "Returns the current context."
end
end
end
9 changes: 9 additions & 0 deletions test/irb/command/test_help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,14 @@ def test_help_lists_user_aliases
assert_match(/\$\s+Alias for `show_source`/, out)
assert_match(/@\s+Alias for `whereami`/, out)
end

def test_help_lists_helper_methods
out = run_ruby_file do
type "help"
type "exit"
end

assert_match(/Helper methods\s+conf\s+Returns the current context/, out)
end
end
end
127 changes: 127 additions & 0 deletions test/irb/test_helper_method.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# frozen_string_literal: true
require "irb"

require_relative "helper"

module TestIRB
class HelperMethodTestCase < TestCase
def setup
$VERBOSE = nil
@verbosity = $VERBOSE
save_encodings
IRB.instance_variable_get(:@CONF).clear
end

def teardown
$VERBOSE = @verbosity
restore_encodings
end

def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
IRB.init_config(nil)
IRB.conf[:VERBOSE] = false
IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf.merge!(conf)
input = TestInputMethod.new(lines)
irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
irb.context.return_format = "=> %s\n"
irb.context.irb_path = irb_path if irb_path
IRB.conf[:MAIN_CONTEXT] = irb.context
IRB.conf[:USE_PAGER] = false
capture_output do
irb.eval_input
end
end
end

module TestHelperMethod
class ConfTest < HelperMethodTestCase
def test_conf_returns_the_context_object
out, err = execute_lines("conf.ap_name")

assert_empty err
assert_include out, "=> \"irb\""
end
end

class ContextTest < HelperMethodTestCase
def test_context_returns_the_context_object_and_prints_deprecation_warning
out, err = execute_lines("context.ap_name")

assert_empty err
assert_include out, "=> \"irb\""
end
end

class IrbContextTest < HelperMethodTestCase
def test_context_returns_the_context_object_and_prints_deprecation_warning
out, err = execute_lines("irb_context.ap_name")

assert_empty err
assert_include out, "=> \"irb\""
end
end
end

class HelperMethodIntegrationTest < IntegrationTestCase
def test_arguments_propogation
write_ruby <<~RUBY
require "irb/helper_method"
class MyHelper < IRB::HelperMethod::Base
description "This is a test helper"
def execute(
required_arg, optional_arg = nil, *splat_arg, required_keyword_arg:,
optional_keyword_arg: nil, **double_splat_arg, &block_arg
)
puts [required_arg, optional_arg, splat_arg, required_keyword_arg, optional_keyword_arg, double_splat_arg, block_arg.call].to_s
end
end
IRB::HelperMethod.register(:my_helper, MyHelper)
binding.irb
RUBY

output = run_ruby_file do
type <<~INPUT
my_helper(
"required", "optional", "splat", required_keyword_arg: "required",
optional_keyword_arg: "optional", a: 1, b: 2
) { "block" }
INPUT
type "exit"
end

assert_include(output, '["required", "optional", ["splat"], "required", "optional", {:a=>1, :b=>2}, "block"]')
end

def test_helper_method_injection_can_happen_after_irb_require
write_ruby <<~RUBY
require "irb"
class MyHelper < IRB::HelperMethod::Base
description "This is a test helper"
def execute
puts "Hello from MyHelper"
end
end
IRB::HelperMethod.register(:my_helper, MyHelper)
binding.irb
RUBY

output = run_ruby_file do
type <<~INPUT
my_helper
INPUT
type "exit"
end

assert_include(output, 'Hello from MyHelper')
end
end
end

0 comments on commit 29f8ed7

Please sign in to comment.