irb 1.14.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +5 -0
  3. data/CONTRIBUTING.md +52 -0
  4. data/EXTEND_IRB.md +3 -0
  5. data/Gemfile +11 -1
  6. data/README.md +13 -293
  7. data/doc/COMMAND_LINE_OPTIONS.md +69 -0
  8. data/doc/COMPARED_WITH_PRY.md +22 -0
  9. data/doc/Configurations.md +274 -0
  10. data/doc/EXTEND_IRB.md +122 -0
  11. data/doc/Index.md +705 -0
  12. data/doc/irb/irb.rd.ja +1 -1
  13. data/lib/irb/color.rb +251 -162
  14. data/lib/irb/color_printer.rb +10 -9
  15. data/lib/irb/command/base.rb +46 -13
  16. data/lib/irb/command/copy.rb +83 -0
  17. data/lib/irb/command/debug.rb +8 -6
  18. data/lib/irb/command/help.rb +1 -1
  19. data/lib/irb/command/history.rb +1 -1
  20. data/lib/irb/command/internal_helpers.rb +7 -4
  21. data/lib/irb/command/ls.rb +29 -15
  22. data/lib/irb/completion.rb +81 -57
  23. data/lib/irb/context.rb +132 -53
  24. data/lib/irb/debug/ui.rb +2 -4
  25. data/lib/irb/debug.rb +9 -12
  26. data/lib/irb/default_commands.rb +19 -8
  27. data/lib/irb/easter-egg.rb +13 -7
  28. data/lib/irb/ext/multi-irb.rb +2 -0
  29. data/lib/irb/history.rb +84 -55
  30. data/lib/irb/init.rb +7 -2
  31. data/lib/irb/input-method.rb +167 -130
  32. data/lib/irb/inspector.rb +42 -36
  33. data/lib/irb/lc/help-message +2 -2
  34. data/lib/irb/lc/ja/help-message +4 -4
  35. data/lib/irb/nesting_parser.rb +364 -213
  36. data/lib/irb/pager.rb +137 -7
  37. data/lib/irb/ruby-lex.rb +230 -302
  38. data/lib/irb/ruby_logo.aa +79 -37
  39. data/lib/irb/source_finder.rb +15 -22
  40. data/lib/irb/startup_message.rb +83 -0
  41. data/lib/irb/statement.rb +21 -0
  42. data/lib/irb/version.rb +2 -2
  43. data/lib/irb/workspace.rb +22 -31
  44. data/lib/irb.rb +148 -986
  45. data/man/irb.1 +37 -2
  46. metadata +42 -12
  47. data/.document +0 -4
  48. data/Rakefile +0 -54
  49. data/bin/console +0 -6
  50. data/bin/setup +0 -6
  51. data/irb.gemspec +0 -46
data/lib/irb/context.rb CHANGED
@@ -13,6 +13,10 @@ module IRB
13
13
  # A class that wraps the current state of the irb session, including the
14
14
  # configuration of IRB.conf.
15
15
  class Context
16
+ KERNEL_PUBLIC_METHOD = ::Kernel.instance_method(:public_method)
17
+ KERNEL_METHOD = ::Kernel.instance_method(:method)
18
+
19
+ ASSIGN_OPERATORS_REGEXP = Regexp.union(%w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=])
16
20
  # Creates a new IRB context.
17
21
  #
18
22
  # The optional +input_method+ argument:
@@ -148,26 +152,9 @@ module IRB
148
152
  @newline_before_multiline_output = true
149
153
  end
150
154
 
151
- @user_aliases = IRB.conf[:COMMAND_ALIASES].dup
152
- @command_aliases = @user_aliases.merge(KEYWORD_ALIASES)
153
- end
154
-
155
- private def term_interactive?
156
- return true if ENV['TEST_IRB_FORCE_INTERACTIVE']
157
- STDIN.tty? && ENV['TERM'] != 'dumb'
155
+ @command_aliases = IRB.conf[:COMMAND_ALIASES].dup
158
156
  end
159
157
 
160
- # because all input will eventually be evaluated as Ruby code,
161
- # command names that conflict with Ruby keywords need special workaround
162
- # we can remove them once we implemented a better command system for IRB
163
- KEYWORD_ALIASES = {
164
- :break => :irb_break,
165
- :catch => :irb_catch,
166
- :next => :irb_next,
167
- }.freeze
168
-
169
- private_constant :KEYWORD_ALIASES
170
-
171
158
  def use_tracer=(val)
172
159
  require_relative "ext/tracer" if val
173
160
  IRB.conf[:USE_TRACER] = val
@@ -185,39 +172,6 @@ module IRB
185
172
  __send__(__method__, val)
186
173
  end
187
174
 
188
- private def build_completor
189
- completor_type = IRB.conf[:COMPLETOR]
190
- case completor_type
191
- when :regexp
192
- return RegexpCompletor.new
193
- when :type
194
- completor = build_type_completor
195
- return completor if completor
196
- else
197
- warn "Invalid value for IRB.conf[:COMPLETOR]: #{completor_type}"
198
- end
199
- # Fallback to RegexpCompletor
200
- RegexpCompletor.new
201
- end
202
-
203
- private def build_type_completor
204
- if RUBY_ENGINE == 'truffleruby'
205
- # Avoid SyntaxError. truffleruby does not support endless method definition yet.
206
- warn 'TypeCompletor is not supported on TruffleRuby yet'
207
- return
208
- end
209
-
210
- begin
211
- require 'repl_type_completor'
212
- rescue LoadError => e
213
- warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}"
214
- return
215
- end
216
-
217
- ReplTypeCompletor.preload_rbs
218
- TypeCompletor.new(self)
219
- end
220
-
221
175
  def save_history=(val)
222
176
  IRB.conf[:SAVE_HISTORY] = val
223
177
  end
@@ -310,6 +264,8 @@ module IRB
310
264
  attr_reader :use_autocomplete
311
265
  # A copy of the default <code>IRB.conf[:INSPECT_MODE]</code>
312
266
  attr_reader :inspect_mode
267
+ # Inspector for the current context
268
+ attr_reader :inspect_method
313
269
 
314
270
  # A copy of the default <code>IRB.conf[:PROMPT_MODE]</code>
315
271
  attr_reader :prompt_mode
@@ -602,6 +558,8 @@ module IRB
602
558
  set_last_value(result)
603
559
  when Statement::Command
604
560
  statement.command_class.execute(self, statement.arg)
561
+ when Statement::IncorrectAlias
562
+ warn statement.message
605
563
  end
606
564
 
607
565
  nil
@@ -635,8 +593,78 @@ module IRB
635
593
  result
636
594
  end
637
595
 
638
- def inspect_last_value # :nodoc:
639
- @inspect_method.inspect_value(@last_value)
596
+ def parse_input(code, is_assignment_expression)
597
+ command_name, arg = code.strip.split(/\s+/, 2)
598
+ arg ||= ''
599
+
600
+ # command can only be 1 line
601
+ if code.lines.size != 1 ||
602
+ # command name is required
603
+ command_name.nil? ||
604
+ # local variable have precedence over command
605
+ local_variables.include?(command_name.to_sym) ||
606
+ # assignment expression is not a command
607
+ (is_assignment_expression ||
608
+ (arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/)))
609
+ return Statement::Expression.new(code, is_assignment_expression)
610
+ end
611
+
612
+ command = command_name.to_sym
613
+
614
+ # Check command aliases
615
+ if aliased_name = command_aliases[command]
616
+ if command_class = Command.load_command(aliased_name)
617
+ command = aliased_name
618
+ elsif HelperMethod.helper_methods[aliased_name]
619
+ message = <<~MESSAGE
620
+ Using command alias `#{command}` for helper method `#{aliased_name}` is not supported.
621
+ Please check the value of `IRB.conf[:COMMAND_ALIASES]`.
622
+ MESSAGE
623
+ return Statement::IncorrectAlias.new(message)
624
+ else
625
+ message = <<~MESSAGE
626
+ You're trying to use command alias `#{command}` for command `#{aliased_name}`, but `#{aliased_name}` does not exist.
627
+ Please check the value of `IRB.conf[:COMMAND_ALIASES]`.
628
+ MESSAGE
629
+ return Statement::IncorrectAlias.new(message)
630
+ end
631
+ else
632
+ command_class = Command.load_command(command)
633
+ end
634
+
635
+ if command_class
636
+ # Check whether the command conflicts with existing methods
637
+ public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false
638
+ private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false
639
+ if Command.execute_as_command?(command, public_method: public_method, private_method: private_method)
640
+ return Statement::Command.new(code, command_class, arg)
641
+ end
642
+ end
643
+ Statement::Expression.new(code, is_assignment_expression)
644
+ end
645
+
646
+ def colorize_input(input, complete:)
647
+ if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable?
648
+ lvars = local_variables || []
649
+ parsed_input = parse_input(input, false)
650
+ if parsed_input.is_a?(Statement::Command)
651
+ name, sep, arg = input.split(/(\s+)/, 2)
652
+ arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) if arg
653
+ "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}"
654
+ else
655
+ IRB::Color.colorize_code(input, complete: complete, local_variables: lvars)
656
+ end
657
+ else
658
+ Reline::Unicode.escape_for_print(input)
659
+ end
660
+ end
661
+
662
+ def inspect_last_value(output = +'') # :nodoc:
663
+ @inspect_method.inspect_value(@last_value, output)
664
+ end
665
+
666
+ def inspector_support_stream_output?
667
+ @inspect_method.support_stream_output?
640
668
  end
641
669
 
642
670
  NOPRINTING_IVARS = ["@last_value"] # :nodoc:
@@ -669,5 +697,56 @@ module IRB
669
697
  def local_variables # :nodoc:
670
698
  workspace.binding.local_variables
671
699
  end
700
+
701
+ def safe_method_call_on_main(method_name)
702
+ main_object = main
703
+ Object === main_object ? main_object.__send__(method_name) : Object.instance_method(method_name).bind_call(main_object)
704
+ end
705
+
706
+ private
707
+
708
+ def term_interactive?
709
+ return true if ENV['TEST_IRB_FORCE_INTERACTIVE']
710
+ STDIN.tty? && ENV['TERM'] != 'dumb'
711
+ end
712
+
713
+ def build_completor
714
+ completor_type = IRB.conf[:COMPLETOR]
715
+
716
+ # Gem repl_type_completor is added to bundled gems in Ruby 3.4.
717
+ # Use :type as default completor only in Ruby 3.4 or later.
718
+ verbose = !!completor_type
719
+ completor_type ||= RUBY_VERSION >= '3.4' ? :type : :regexp
720
+
721
+ case completor_type
722
+ when :regexp
723
+ return RegexpCompletor.new
724
+ when :type
725
+ completor = build_type_completor(verbose: verbose)
726
+ return completor if completor
727
+ else
728
+ warn "Invalid value for IRB.conf[:COMPLETOR]: #{completor_type}"
729
+ end
730
+ # Fallback to RegexpCompletor
731
+ RegexpCompletor.new
732
+ end
733
+
734
+ def build_type_completor(verbose:)
735
+ if RUBY_ENGINE == 'truffleruby'
736
+ # Avoid SyntaxError. truffleruby does not support endless method definition yet.
737
+ warn 'TypeCompletor is not supported on TruffleRuby yet' if verbose
738
+ return
739
+ end
740
+
741
+ begin
742
+ require 'repl_type_completor'
743
+ rescue LoadError => e
744
+ warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" if verbose
745
+ return
746
+ end
747
+
748
+ ReplTypeCompletor.preload_rbs
749
+ TypeCompletor.new(self)
750
+ end
672
751
  end
673
752
  end
data/lib/irb/debug/ui.rb CHANGED
@@ -45,9 +45,7 @@ module IRB
45
45
  $stdout.puts line.chomp
46
46
  }
47
47
  when String
48
- str.each_line{|line|
49
- $stdout.puts line.chomp
50
- }
48
+ Pager.page_content(str, retain_content: true)
51
49
  when nil
52
50
  $stdout.puts
53
51
  end
@@ -56,7 +54,7 @@ module IRB
56
54
  def readline _
57
55
  setup_interrupt do
58
56
  tc = DEBUGGER__::SESSION.instance_variable_get(:@tc)
59
- cmd = @irb.debug_readline(tc.current_frame.binding || TOPLEVEL_BINDING)
57
+ cmd = @irb.debug_readline(tc.current_frame.eval_binding || TOPLEVEL_BINDING)
60
58
 
61
59
  case cmd
62
60
  when nil # when user types C-d
data/lib/irb/debug.rb CHANGED
@@ -49,7 +49,7 @@ module IRB
49
49
  def DEBUGGER__.capture_frames(*args)
50
50
  frames = capture_frames_without_irb(*args)
51
51
  frames.reject! do |frame|
52
- frame.realpath&.start_with?(IRB_DIR) || frame.path == "<internal:prelude>"
52
+ frame.realpath&.start_with?(IRB_DIR) || frame.path&.start_with?("<internal:")
53
53
  end
54
54
  frames
55
55
  end
@@ -57,22 +57,18 @@ module IRB
57
57
  DEBUGGER__::ThreadClient.prepend(SkipPathHelperForIRB)
58
58
  end
59
59
 
60
- if !@output_modifier_defined && !DEBUGGER__::CONFIG[:no_hint]
61
- irb_output_modifier_proc = Reline.output_modifier_proc
62
-
63
- Reline.output_modifier_proc = proc do |output, complete:|
64
- unless output.strip.empty?
65
- cmd = output.split(/\s/, 2).first
60
+ if !DEBUGGER__::CONFIG[:no_hint] && irb.context.io.is_a?(RelineInputMethod)
61
+ Reline.output_modifier_proc = proc do |input, complete:|
62
+ unless input.strip.empty?
63
+ cmd = input[/\S+/]
66
64
 
67
65
  if !complete && DEBUGGER__.commands.key?(cmd)
68
- output = output.sub(/\n$/, " # debug command\n")
66
+ input = input.sub(/\n$/, " # debug command\n")
69
67
  end
70
68
  end
71
69
 
72
- irb_output_modifier_proc.call(output, complete: complete)
70
+ irb.context.colorize_input(input, complete: complete)
73
71
  end
74
-
75
- @output_modifier_defined = true
76
72
  end
77
73
 
78
74
  true
@@ -85,12 +81,13 @@ module IRB
85
81
  IRB.instance_variable_set(:@debugger_irb, irb)
86
82
  irb.context.with_debugger = true
87
83
  irb.context.irb_name += ":rdbg"
84
+ irb.context.io.load_history if irb.context.io.class < HistorySavingAbility
88
85
  end
89
86
 
90
87
  module SkipPathHelperForIRB
91
88
  def skip_internal_path?(path)
92
89
  # The latter can be removed once https://github.com/ruby/debug/issues/866 is resolved
93
- super || path.match?(IRB_DIR) || path.match?('<internal:prelude>')
90
+ super || path&.match?(IRB_DIR) || path&.match?('<internal:prelude>')
94
91
  end
95
92
  end
96
93
 
@@ -9,6 +9,7 @@ require_relative "command/cd"
9
9
  require_relative "command/chws"
10
10
  require_relative "command/context"
11
11
  require_relative "command/continue"
12
+ require_relative "command/copy"
12
13
  require_relative "command/debug"
13
14
  require_relative "command/delete"
14
15
  require_relative "command/disable_irb"
@@ -181,9 +182,15 @@ module IRB
181
182
  [:edit, NO_OVERRIDE]
182
183
  )
183
184
 
184
- _register_with_aliases(:irb_break, Command::Break)
185
- _register_with_aliases(:irb_catch, Command::Catch)
186
- _register_with_aliases(:irb_next, Command::Next)
185
+ _register_with_aliases(:irb_break, Command::Break,
186
+ [:break, OVERRIDE_ALL]
187
+ )
188
+ _register_with_aliases(:irb_catch, Command::Catch,
189
+ [:catch, OVERRIDE_PRIVATE_ONLY]
190
+ )
191
+ _register_with_aliases(:irb_next, Command::Next,
192
+ [:next, OVERRIDE_ALL]
193
+ )
187
194
  _register_with_aliases(:irb_delete, Command::Delete,
188
195
  [:delete, NO_OVERRIDE]
189
196
  )
@@ -212,7 +219,8 @@ module IRB
212
219
  )
213
220
 
214
221
  _register_with_aliases(:irb_show_doc, Command::ShowDoc,
215
- [:show_doc, NO_OVERRIDE]
222
+ [:show_doc, NO_OVERRIDE],
223
+ [:ri, NO_OVERRIDE]
216
224
  )
217
225
 
218
226
  _register_with_aliases(:irb_info, Command::IrbInfo)
@@ -243,6 +251,7 @@ module IRB
243
251
  )
244
252
 
245
253
  register(:cd, Command::CD)
254
+ register(:copy, Command::Copy)
246
255
  end
247
256
 
248
257
  ExtendCommand = Command
@@ -259,10 +268,12 @@ module IRB
259
268
  # Deprecated. Doesn't have any effect.
260
269
  @EXTEND_COMMANDS = []
261
270
 
262
- # Drepcated. Use Command.regiser instead.
263
- def self.def_extend_command(cmd_name, cmd_class, _, *aliases)
264
- Command._register_with_aliases(cmd_name, cmd_class, *aliases)
265
- Command.class_variable_set(:@@command_override_policies, nil)
271
+ class << self
272
+ # Drepcated. Use Command.regiser instead.
273
+ def def_extend_command(cmd_name, cmd_class, _, *aliases)
274
+ Command._register_with_aliases(cmd_name, cmd_class, *aliases)
275
+ Command.class_variable_set(:@@command_override_policies, nil)
276
+ end
266
277
  end
267
278
  end
268
279
  end
@@ -100,31 +100,34 @@ module IRB
100
100
 
101
101
  private def easter_egg_logo(type)
102
102
  @easter_egg_logos ||= File.read(File.join(__dir__, 'ruby_logo.aa'), encoding: 'UTF-8:UTF-8')
103
- .split(/TYPE: ([A-Z]+)\n/)[1..]
103
+ .split(/TYPE: ([A-Z_]+)\n/)[1..]
104
104
  .each_slice(2)
105
105
  .to_h
106
106
  @easter_egg_logos[type.to_s.upcase]
107
107
  end
108
108
 
109
109
  private def easter_egg(type = nil)
110
+ print "\e[?1049h"
110
111
  type ||= [:logo, :dancing].sample
111
112
  case type
112
113
  when :logo
113
- require "rdoc"
114
- RDoc::RI::Driver.new.page do |io|
115
- io.write easter_egg_logo(:large)
114
+ Pager.page do |io|
115
+ logo_type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large
116
+ io.write easter_egg_logo(logo_type)
117
+ STDIN.raw { STDIN.getc } if io == STDOUT
116
118
  end
117
119
  when :dancing
118
120
  STDOUT.cooked do
119
121
  interrupted = false
120
122
  prev_trap = trap("SIGINT") { interrupted = true }
121
123
  canvas = Canvas.new(Reline.get_screen_size)
124
+ otio = Reline::IOGate.prep
122
125
  Reline::IOGate.set_winch_handler do
123
126
  canvas = Canvas.new(Reline.get_screen_size)
124
127
  end
125
128
  ruby_model = RubyModel.new
126
- print "\e[?1049h"
127
- 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later
129
+ print "\e[?25l" # hide cursor
130
+ (0..).each do |i|
128
131
  buff = canvas.draw do
129
132
  ruby_model.render_frame(i) do |p1, p2|
130
133
  canvas.line(p1, p2)
@@ -137,10 +140,13 @@ module IRB
137
140
  end
138
141
  rescue Interrupt
139
142
  ensure
140
- print "\e[0m\e[?1049l"
143
+ Reline::IOGate.deprep(otio)
144
+ print "\e[?25h" # show cursor
141
145
  trap("SIGINT", prev_trap)
142
146
  end
143
147
  end
148
+ ensure
149
+ print "\e[0m\e[?1049l"
144
150
  end
145
151
  end
146
152
  end
@@ -65,6 +65,7 @@ module IRB
65
65
  @current_job = irb
66
66
  th.run
67
67
  Thread.stop
68
+ Thread.pass
68
69
  @current_job = irb(Thread.current)
69
70
  end
70
71
 
@@ -220,6 +221,7 @@ module IRB
220
221
  end
221
222
  end
222
223
  Thread.stop
224
+ Thread.pass
223
225
  @JobManager.current_job = @JobManager.irb(Thread.current)
224
226
  end
225
227
 
data/lib/irb/history.rb CHANGED
@@ -1,6 +1,36 @@
1
1
  require "pathname"
2
2
 
3
3
  module IRB
4
+ module History
5
+ DEFAULT_ENTRY_LIMIT = 1000
6
+
7
+ class << self
8
+ # Integer representation of <code>IRB.conf[:HISTORY_FILE]</code>.
9
+ def save_history
10
+ return 0 if IRB.conf[:SAVE_HISTORY] == false
11
+ return DEFAULT_ENTRY_LIMIT if IRB.conf[:SAVE_HISTORY] == true
12
+ IRB.conf[:SAVE_HISTORY].to_i
13
+ end
14
+
15
+ def save_history?
16
+ !save_history.zero?
17
+ end
18
+
19
+ def infinite?
20
+ save_history.negative?
21
+ end
22
+
23
+ # Might be nil when HOME and XDG_CONFIG_HOME are not available.
24
+ def history_file
25
+ if (history_file = IRB.conf[:HISTORY_FILE])
26
+ File.expand_path(history_file)
27
+ else
28
+ IRB.rc_file("_history")
29
+ end
30
+ end
31
+ end
32
+ end
33
+
4
34
  module HistorySavingAbility # :nodoc:
5
35
  def support_history_saving?
6
36
  true
@@ -11,76 +41,75 @@ module IRB
11
41
  end
12
42
 
13
43
  def load_history
44
+ history_file = History.history_file
45
+ return unless File.exist?(history_file.to_s)
46
+
14
47
  history = self.class::HISTORY
15
48
 
16
- if history_file = IRB.conf[:HISTORY_FILE]
17
- history_file = File.expand_path(history_file)
18
- end
19
- history_file = IRB.rc_file("_history") unless history_file
20
- if history_file && File.exist?(history_file)
21
- File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f|
22
- f.each { |l|
23
- l = l.chomp
24
- if self.class == RelineInputMethod and history.last&.end_with?("\\")
25
- history.last.delete_suffix!("\\")
26
- history.last << "\n" << l
27
- else
28
- history << l
29
- end
30
- }
31
- end
32
- @loaded_history_lines = history.size
33
- @loaded_history_mtime = File.mtime(history_file)
49
+ File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f|
50
+ f.each { |l|
51
+ l = l.chomp
52
+ if self.class == RelineInputMethod and history.last&.end_with?("\\")
53
+ history.last.delete_suffix!("\\")
54
+ history.last << "\n" << l
55
+ else
56
+ history << l
57
+ end
58
+ }
34
59
  end
60
+ @loaded_history_lines = history.size
61
+ @loaded_history_mtime = File.mtime(history_file)
35
62
  end
36
63
 
37
64
  def save_history
65
+ return unless History.save_history?
66
+ return unless (history_file = History.history_file)
67
+ unless ensure_history_file_writable(history_file)
68
+ warn <<~WARN
69
+ Can't write history to #{History.history_file.inspect} due to insufficient permissions.
70
+ Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable.
71
+ WARN
72
+ return
73
+ end
74
+
38
75
  history = self.class::HISTORY.to_a
39
76
 
40
- if num = IRB.conf[:SAVE_HISTORY] and (num = num.to_i) != 0
41
- if history_file = IRB.conf[:HISTORY_FILE]
42
- history_file = File.expand_path(history_file)
43
- end
44
- history_file = IRB.rc_file("_history") unless history_file
77
+ if File.exist?(history_file) &&
78
+ File.mtime(history_file) != @loaded_history_mtime
79
+ history = history[@loaded_history_lines..-1] if @loaded_history_lines
80
+ append_history = true
81
+ end
45
82
 
46
- # When HOME and XDG_CONFIG_HOME are not available, history_file might be nil
47
- return unless history_file
83
+ File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f|
84
+ hist = history.map { |l| l.scrub.split("\n").join("\\\n") }
48
85
 
49
- # Change the permission of a file that already exists[BUG #7694]
50
- begin
51
- if File.stat(history_file).mode & 066 != 0
52
- File.chmod(0600, history_file)
53
- end
54
- rescue Errno::ENOENT
55
- rescue Errno::EPERM
56
- return
57
- rescue
58
- raise
86
+ unless append_history || History.infinite?
87
+ # Check size before slicing because array.last(huge_number) raises RangeError.
88
+ hist = hist.last(History.save_history) if hist.size > History.save_history
59
89
  end
60
90
 
61
- if File.exist?(history_file) &&
62
- File.mtime(history_file) != @loaded_history_mtime
63
- history = history[@loaded_history_lines..-1] if @loaded_history_lines
64
- append_history = true
65
- end
91
+ f.puts(hist)
92
+ end
93
+ end
66
94
 
67
- pathname = Pathname.new(history_file)
68
- unless Dir.exist?(pathname.dirname)
69
- warn "Warning: The directory to save IRB's history file does not exist. Please double check `IRB.conf[:HISTORY_FILE]`'s value."
70
- return
71
- end
95
+ private
72
96
 
73
- File.open(history_file, (append_history ? 'a' : 'w'), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f|
74
- hist = history.map{ |l| l.scrub.split("\n").join("\\\n") }
75
- unless append_history
76
- begin
77
- hist = hist.last(num) if hist.size > num and num > 0
78
- rescue RangeError # bignum too big to convert into `long'
79
- # Do nothing because the bignum should be treated as infinity
80
- end
81
- end
82
- f.puts(hist)
97
+ # Returns boolean whether writing to +history_file+ will be possible.
98
+ # Permissions of already existing +history_file+ are changed to
99
+ # owner-only-readable if necessary [BUG #7694].
100
+ def ensure_history_file_writable(history_file)
101
+ history_file = Pathname.new(history_file)
102
+
103
+ return false unless history_file.dirname.writable?
104
+ return true unless history_file.exist?
105
+
106
+ begin
107
+ if history_file.stat.mode & 0o66 != 0
108
+ history_file.chmod 0o600
83
109
  end
110
+ true
111
+ rescue Errno::EPERM # no permissions
112
+ false
84
113
  end
85
114
  end
86
115
  end
data/lib/irb/init.rb CHANGED
@@ -80,20 +80,21 @@ module IRB # :nodoc:
80
80
  @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod)
81
81
  @CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty?
82
82
  @CONF[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "true") != "false"
83
- @CONF[:COMPLETOR] = ENV.fetch("IRB_COMPLETOR", "regexp").to_sym
83
+ @CONF[:COMPLETOR] = ENV["IRB_COMPLETOR"]&.to_sym
84
84
  @CONF[:INSPECT_MODE] = true
85
85
  @CONF[:USE_TRACER] = false
86
86
  @CONF[:USE_LOADER] = false
87
87
  @CONF[:IGNORE_SIGINT] = true
88
88
  @CONF[:IGNORE_EOF] = false
89
89
  @CONF[:USE_PAGER] = true
90
+ @CONF[:SHOW_BANNER] = true
90
91
  @CONF[:EXTRA_DOC_DIRS] = []
91
92
  @CONF[:ECHO] = nil
92
93
  @CONF[:ECHO_ON_ASSIGNMENT] = nil
93
94
  @CONF[:VERBOSE] = nil
94
95
 
95
96
  @CONF[:EVAL_HISTORY] = nil
96
- @CONF[:SAVE_HISTORY] = 1000
97
+ @CONF[:SAVE_HISTORY] = History::DEFAULT_ENTRY_LIMIT
97
98
 
98
99
  @CONF[:BACK_TRACE_LIMIT] = 16
99
100
 
@@ -194,6 +195,8 @@ module IRB # :nodoc:
194
195
  :'$' => :show_source,
195
196
  :'@' => :whereami,
196
197
  }
198
+
199
+ @CONF[:COPY_COMMAND] = ENV.fetch("IRB_COPY_COMMAND", nil)
197
200
  end
198
201
 
199
202
  def IRB.set_measure_callback(type = nil, arg = nil, &block)
@@ -343,6 +346,8 @@ module IRB # :nodoc:
343
346
  opt = $1 || argv.shift
344
347
  prompt_mode = opt.upcase.tr("-", "_").intern
345
348
  @CONF[:PROMPT_MODE] = prompt_mode
349
+ when "--nobanner"
350
+ @CONF[:SHOW_BANNER] = false
346
351
  when "--noprompt"
347
352
  @CONF[:PROMPT_MODE] = :NULL
348
353
  when "--script"