Skip to content

Commit fe2c3c8

Browse files
committed
Add startup banner with Ruby logo, version info, and rotating tips
Display a 3-line startup banner when IRB is launched via the CLI executable. The banner shows a small braille Ruby logo alongside the IRB/Ruby version, a randomly selected usage tip, and the current working directory. The banner is gated on ap_path matching "irb" so it only appears for CLI usage, not binding.irb or embedded IRB. Users can disable it with IRB.conf[:SHOW_BANNER] = false in their .irbrc.
1 parent 6b6874b commit fe2c3c8

7 files changed

Lines changed: 169 additions & 1 deletion

File tree

lib/irb.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
require_relative "irb/version"
2323
require_relative "irb/easter-egg"
24+
require_relative "irb/startup_message"
2425
require_relative "irb/debug"
2526
require_relative "irb/pager"
2627

@@ -50,7 +51,13 @@ def start(ap_path = nil)
5051
irb = Irb.new(nil, @CONF[:SCRIPT])
5152
else
5253
irb = Irb.new
54+
55+
# Only display the banner in the irb executable
56+
if @CONF[:SHOW_BANNER] && ap_path&.end_with?("exe/irb")
57+
StartupMessage.display
58+
end
5359
end
60+
5461
irb.run(@CONF)
5562
end
5663

lib/irb/init.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def IRB.init_config(ap_path)
8787
@CONF[:IGNORE_SIGINT] = true
8888
@CONF[:IGNORE_EOF] = false
8989
@CONF[:USE_PAGER] = true
90+
@CONF[:SHOW_BANNER] = true
9091
@CONF[:EXTRA_DOC_DIRS] = []
9192
@CONF[:ECHO] = nil
9293
@CONF[:ECHO_ON_ASSIGNMENT] = nil

lib/irb/ruby_logo.aa

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,7 @@ TYPE: UNICODE
116116
⢻⣾⡇ ⠘⣷ ⣼⠃ ⠘⣷⣠⣴⠟⠋ ⠙⢷⣄⢸⣿
117117
⠻⣧⡀ ⠘⣧⣰⡏ ⢀⣠⣤⠶⠛⠉⠛⠛⠛⠛⠛⠛⠻⢶⣶⣶⣶⣶⣶⣤⣤⣽⣿⣿
118118
⠈⠛⠷⢦⣤⣽⣿⣥⣤⣶⣶⡿⠿⠿⠶⠶⠶⠶⠾⠛⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠁
119+
TYPE: UNICODE_SMALL
120+
⢀⡴⠊⢉⡟⢟
121+
⣎⣀⣴⡋⡟⣻
122+
⣟⣼⣱⣽⣟⣾

lib/irb/startup_message.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "color"
4+
require_relative "version"
5+
6+
module IRB
7+
module StartupMessage
8+
TIPS = [
9+
'Type "help" for commands, "help <cmd>" for details',
10+
'"show_doc method" to view documentation',
11+
'"ls [object]" to see methods and properties',
12+
'"ls [object] -g pattern" to filter methods and properties',
13+
'"edit method" to open the method\'s source in editor',
14+
'"cd object" to navigate into an object',
15+
'"show_source method" to view source code',
16+
'"copy expr" to copy the output to clipboard',
17+
'"debug" to start integration with the "debug" gem',
18+
'"history -g pattern" to search history',
19+
].freeze
20+
21+
class << self
22+
def display
23+
logo_lines = load_logo
24+
info_lines = build_info_lines
25+
26+
output = if logo_lines
27+
combine_logo_and_info(logo_lines, info_lines)
28+
else
29+
info_lines.join("\n")
30+
end
31+
32+
# Add a blank line to not immediately touch warning messages
33+
puts
34+
puts output
35+
puts
36+
end
37+
38+
private
39+
40+
def load_logo
41+
encoding = STDOUT.external_encoding || Encoding.default_external
42+
return nil unless encoding == Encoding::UTF_8
43+
44+
logo = IRB.send(:easter_egg_logo, :unicode_small)
45+
return nil unless logo
46+
47+
logo.chomp.lines.map(&:chomp)
48+
end
49+
50+
def build_info_lines
51+
version_line = "#{Color.colorize('IRB', [:BOLD])} v#{VERSION} \u2014 Ruby #{RUBY_VERSION}"
52+
tip_line = colorize_tip(TIPS.sample)
53+
dir_line = Color.colorize(short_pwd, [:CYAN])
54+
55+
[version_line, tip_line, dir_line]
56+
end
57+
58+
def colorize_tip(tip)
59+
tip.gsub(/"[^"]*"/) { |match| Color.colorize(match, [:YELLOW]) }
60+
end
61+
62+
def combine_logo_and_info(logo_lines, info_lines)
63+
max_lines = [logo_lines.size, info_lines.size].max
64+
lines = max_lines.times.map do |i|
65+
logo_part = logo_lines[i] || ""
66+
info_part = info_lines[i] || ""
67+
colored_logo = Color.colorize(logo_part, [:RED, :BOLD])
68+
"#{colored_logo} #{info_part}"
69+
end
70+
lines.join("\n")
71+
end
72+
73+
def short_pwd
74+
dir = Dir.pwd
75+
home = ENV['HOME']
76+
if home && (dir == home || dir.start_with?("#{home}/"))
77+
dir = "~#{dir[home.size..]}"
78+
end
79+
dir
80+
end
81+
end
82+
end
83+
end

test/irb/helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def run_ruby_file(timeout: TIMEOUT_SEC, via_irb: false, &block)
166166
@envs["XDG_CONFIG_HOME"] ||= tmp_dir
167167
@envs["IRBRC"] = nil unless @envs.key?("IRBRC")
168168

169-
envs_for_spawn = @envs.merge('TERM' => 'dumb', 'TEST_IRB_FORCE_INTERACTIVE' => 'true')
169+
envs_for_spawn = {'TERM' => 'dumb', 'TEST_IRB_FORCE_INTERACTIVE' => 'true'}.merge(@envs)
170170

171171
PTY.spawn(envs_for_spawn, *cmd) do |read, write, pid|
172172
Timeout.timeout(timeout) do

test/irb/test_startup_message.rb

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# frozen_string_literal: true
2+
3+
require "irb"
4+
5+
require_relative "helper"
6+
7+
module TestIRB
8+
class StartupMessageTest < TestCase
9+
def test_display_includes_version_info
10+
output, = capture_output { IRB::StartupMessage.display }
11+
12+
assert_match(/IRB/, output)
13+
assert_match(/v#{Regexp.escape(IRB::VERSION)}/, output)
14+
assert_match(/Ruby #{Regexp.escape(RUBY_VERSION)}/, output)
15+
end
16+
17+
def test_display_includes_a_tip
18+
output, = capture_output { IRB::StartupMessage.display }
19+
20+
# Strip ANSI codes for comparison since tips have colorized quoted parts
21+
plain = output.gsub(/\e\[\d+m/, "")
22+
assert(
23+
IRB::StartupMessage::TIPS.any? { |tip| plain.include?(tip) },
24+
"Expected output to include one of the tips"
25+
)
26+
end
27+
28+
def test_display_includes_working_directory
29+
output, = capture_output { IRB::StartupMessage.display }
30+
31+
assert_match(/#{Regexp.escape(File.basename(Dir.pwd))}/, output)
32+
end
33+
34+
def test_short_pwd_replaces_home_with_tilde
35+
Dir.mktmpdir do |tmpdir|
36+
tmpdir = File.realpath(tmpdir)
37+
original_home = ENV['HOME']
38+
original_dir = Dir.pwd
39+
ENV['HOME'] = tmpdir
40+
Dir.chdir(tmpdir)
41+
42+
result = IRB::StartupMessage.send(:short_pwd)
43+
assert_equal "~", result
44+
45+
subdir = File.join(tmpdir, "projects")
46+
Dir.mkdir(subdir)
47+
Dir.chdir(subdir)
48+
49+
result = IRB::StartupMessage.send(:short_pwd)
50+
assert_equal "~/projects", result
51+
ensure
52+
ENV['HOME'] = original_home
53+
Dir.chdir(original_dir)
54+
end
55+
end
56+
end
57+
58+
class StartupMessageIntegrationTest < IntegrationTestCase
59+
def test_banner_does_not_appear_on_binding_irb
60+
write_ruby <<~'RUBY'
61+
binding.irb
62+
RUBY
63+
64+
output = run_ruby_file do
65+
type "exit"
66+
end
67+
68+
assert_not_match(/v#{Regexp.escape(IRB::VERSION)}/, output)
69+
end
70+
end
71+
end

test/irb/yamatanooroti/test_rendering.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def setup
2626
@irbrc_backup = ENV['IRBRC']
2727
@irbrc_file = ENV['IRBRC'] = File.join(@tmpdir, 'temporaty_irbrc')
2828
File.unlink(@irbrc_file) if File.exist?(@irbrc_file)
29+
File.write(@irbrc_file, "IRB.conf[:SHOW_BANNER] = false\n")
2930
ENV['HOME'] = File.join(@tmpdir, 'home')
3031
ENV['XDG_CONFIG_HOME'] = File.join(@tmpdir, 'xdg_config_home')
3132
end
@@ -520,6 +521,7 @@ def test_debug_integration_doesnt_hint_debugger_commands_in_nomultiline_mode
520521

521522
def write_irbrc(content)
522523
File.open(@irbrc_file, 'w') do |f|
524+
f.write "IRB.conf[:SHOW_BANNER] = false\n"
523525
f.write content
524526
end
525527
end

0 commit comments

Comments
 (0)