Skip to content

Commit e23db51

Browse files
authored
Allow non-identifier aliases like Pry's @ and $ (#426)
* Allow non-identifier aliases * Move the configuration to IRB.conf * Avoid abusing method lookup for symbol aliases * Add more alias tests * A small optimization * Assume non-nil Context * Load IRB.conf earlier
1 parent 08ac803 commit e23db51

File tree

5 files changed

+87
-0
lines changed

5 files changed

+87
-0
lines changed

lib/irb.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ class Irb
426426
def initialize(workspace = nil, input_method = nil)
427427
@context = Context.new(self, workspace, input_method)
428428
@context.main.extend ExtendCommandBundle
429+
@context.command_aliases.each do |alias_name, cmd_name|
430+
@context.main.install_alias_method(alias_name, cmd_name)
431+
end
429432
@signal_status = :IN_IRB
430433
@scanner = RubyLex.new
431434
end

lib/irb/context.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ def initialize(irb, workspace = nil, input_method = nil)
149149
if @newline_before_multiline_output.nil?
150150
@newline_before_multiline_output = true
151151
end
152+
153+
@command_aliases = IRB.conf[:COMMAND_ALIASES]
152154
end
153155

154156
# The top-level workspace, see WorkSpace#main
@@ -326,6 +328,9 @@ def main
326328
# See IRB@Command+line+options for more command line options.
327329
attr_accessor :back_trace_limit
328330

331+
# User-defined IRB command aliases
332+
attr_accessor :command_aliases
333+
329334
# Alias for #use_multiline
330335
alias use_multiline? use_multiline
331336
# Alias for #use_singleline
@@ -477,6 +482,13 @@ def evaluate(line, line_no, exception: nil) # :nodoc:
477482
line = "begin ::Kernel.raise _; rescue _.class\n#{line}\n""end"
478483
@workspace.local_variable_set(:_, exception)
479484
end
485+
486+
# Transform a non-identifier alias (ex: @, $)
487+
command = line.split(/\s/, 2).first
488+
if original = symbol_alias(command)
489+
line = line.gsub(/\A#{Regexp.escape(command)}/, original.to_s)
490+
end
491+
480492
set_last_value(@workspace.evaluate(self, line, irb_path, line_no))
481493
end
482494

@@ -522,5 +534,11 @@ def inspect # :nodoc:
522534
def local_variables # :nodoc:
523535
workspace.binding.local_variables
524536
end
537+
538+
# Return a command name if it's aliased from the argument and it's not an identifier.
539+
def symbol_alias(command)
540+
return nil if command.match?(/\A\w+\z/)
541+
command_aliases[command.to_sym]
542+
end
525543
end
526544
end

lib/irb/init.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ def IRB.init_config(ap_path)
158158
@CONF[:LC_MESSAGES] = Locale.new
159159

160160
@CONF[:AT_EXIT] = []
161+
162+
@CONF[:COMMAND_ALIASES] = {}
161163
end
162164

163165
def IRB.set_measure_callback(type = nil, arg = nil, &block)

lib/irb/ruby-lex.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ def set_input(io, p = nil, context:, &block)
6565
false
6666
end
6767
else
68+
# Accept any single-line input starting with a non-identifier alias (ex: @, $)
69+
command = code.split(/\s/, 2).first
70+
if context.symbol_alias(command)
71+
next true
72+
end
73+
6874
code.gsub!(/\s*\z/, '').concat("\n")
6975
ltype, indent, continue, code_block_open = check_state(code, context: context)
7076
if ltype or indent > 0 or continue or code_block_open

test/irb/test_cmd.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,24 @@ def test_show_source
570570
assert_match(%r[/irb\.rb], out)
571571
end
572572

573+
def test_show_source_alias
574+
input = TestInputMethod.new([
575+
"$ 'IRB.conf'\n",
576+
])
577+
IRB.init_config(nil)
578+
IRB.conf[:COMMAND_ALIASES] = { :'$' => :show_source }
579+
workspace = IRB::WorkSpace.new(Object.new)
580+
IRB.conf[:VERBOSE] = false
581+
irb = IRB::Irb.new(workspace, input)
582+
IRB.conf[:MAIN_CONTEXT] = irb.context
583+
irb.context.return_format = "=> %s\n"
584+
out, err = capture_output do
585+
irb.eval_input
586+
end
587+
assert_empty err
588+
assert_match(%r[/irb\.rb], out)
589+
end
590+
573591
def test_show_source_end_finder
574592
pend if RUBY_ENGINE == 'truffleruby'
575593
eval(code = <<-EOS, binding, __FILE__, __LINE__ + 1)
@@ -610,5 +628,45 @@ def test_whereami
610628
assert_empty err
611629
assert_match(/^From: .+ @ line \d+ :\n/, out)
612630
end
631+
632+
def test_whereami_alias
633+
input = TestInputMethod.new([
634+
"@\n",
635+
])
636+
IRB.init_config(nil)
637+
IRB.conf[:COMMAND_ALIASES] = { :'@' => :whereami }
638+
workspace = IRB::WorkSpace.new(Object.new)
639+
irb = IRB::Irb.new(workspace, input)
640+
IRB.conf[:MAIN_CONTEXT] = irb.context
641+
out, err = capture_output do
642+
irb.eval_input
643+
end
644+
assert_empty err
645+
assert_match(/^From: .+ @ line \d+ :\n/, out)
646+
end
647+
648+
def test_vars_with_aliases
649+
input = TestInputMethod.new([
650+
"@foo\n",
651+
"$bar\n",
652+
])
653+
IRB.init_config(nil)
654+
IRB.conf[:COMMAND_ALIASES] = {
655+
:'@' => :whereami,
656+
:'$' => :show_source,
657+
}
658+
main = Object.new
659+
main.instance_variable_set(:@foo, "foo")
660+
$bar = "bar"
661+
workspace = IRB::WorkSpace.new(main)
662+
irb = IRB::Irb.new(workspace, input)
663+
IRB.conf[:MAIN_CONTEXT] = irb.context
664+
out, err = capture_output do
665+
irb.eval_input
666+
end
667+
assert_empty err
668+
assert_match(/"foo"/, out)
669+
assert_match(/"bar"/, out)
670+
end
613671
end
614672
end

0 commit comments

Comments
 (0)