irb 1.4.3 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 025d4dccc426fe09dc77b79d7efb8157abc559b89a7455249e5d7e9b2ff1562c
4
- data.tar.gz: fd2cffe5c013c56a5d454ce81331ac1c24b6826c81db6ae6f2240e6e2d5f8c06
3
+ metadata.gz: 33ff0b70fb3730f087bc24a7a8b63253ba1a4ad0416a0ffcd1e5395f26e5d0b2
4
+ data.tar.gz: 9cd418e90733c4cb4658368b2a899b50490ad9dbd7352a1c1df0070c10d9669d
5
5
  SHA512:
6
- metadata.gz: 582d9176beb3f3b9a016f6d44b90b9d781066230f4c64dec93ba5d645ae54df8f7f6f1a5dcaab20899390cd048050eaed6db9b45fdcf44c55a57098013855aa9
7
- data.tar.gz: ab9b30b770c677778cc4871b249a0967177805e543bd8576077099be7d3a5d93e2ef33c35362326cd7f889cd1f855d956c2cb9bd992560029e152c6293720a94
6
+ metadata.gz: ec6102a41d55404aa8e5585243cbe74fcdbe1e1d5bcd88ddae4240adff4281d14200b99c621324515a4e226eff5964644a7e8456d707af2ca7dcf8e5211a0b4b
7
+ data.tar.gz: 0ed81d78ce49803706b36f36e173acb1e27f3e217bd3c6e00e46d4875b6be3c7958a2cb3db146703c3cbf7198094561a562e0fd244f6a61501f9ae5196df343a
data/README.md CHANGED
@@ -40,6 +40,57 @@ irb(main):006:1> end
40
40
 
41
41
  The Readline extension module can be used with irb. Use of Readline is default if it's installed.
42
42
 
43
+ ## Commands
44
+
45
+ The following commands are available on IRB.
46
+
47
+ * `cwws`
48
+ * Show the current workspace.
49
+ * `cb`, `cws`, `chws`
50
+ * Change the current workspace to an object.
51
+ * `bindings`, `workspaces`
52
+ * Show workspaces.
53
+ * `edit`
54
+ * Open a file with the editor command defined with `ENV["EDITOR"]`
55
+ * `edit` - opens the file the current context belongs to (if applicable)
56
+ * `edit foo.rb` - opens `foo.rb`
57
+ * `edit Foo` - opens the location of `Foo`
58
+ * `edit Foo.bar` - opens the location of `Foo.bar`
59
+ * `edit Foo#bar` - opens the location of `Foo#bar`
60
+ * `pushb`, `pushws`
61
+ * Push an object to the workspace stack.
62
+ * `popb`, `popws`
63
+ * Pop a workspace from the workspace stack.
64
+ * `load`
65
+ * Load a Ruby file.
66
+ * `require`
67
+ * Require a Ruby file.
68
+ * `source`
69
+ * Loads a given file in the current session.
70
+ * `irb`
71
+ * Start a child IRB.
72
+ * `jobs`
73
+ * List of current sessions.
74
+ * `fg`
75
+ * Switches to the session of the given number.
76
+ * `kill`
77
+ * Kills the session with the given number.
78
+ * `help`
79
+ * Enter the mode to look up RI documents.
80
+ * `irb_info`
81
+ * Show information about IRB.
82
+ * `ls`
83
+ * Show methods, constants, and variables.
84
+ `-g [query]` or `-G [query]` allows you to filter out the output.
85
+ * `measure`
86
+ * `measure` enables the mode to measure processing time. `measure :off` disables it.
87
+ * `$`, `show_source`
88
+ * Show the source code of a given method or constant.
89
+ * `@`, `whereami`
90
+ * Show the source code around binding.irb again.
91
+ * `debug`
92
+ * Start the debugger of debug.gem.
93
+
43
94
  ## Documentation
44
95
 
45
96
  https://docs.ruby-lang.org/en/master/IRB.html
@@ -0,0 +1,96 @@
1
+ require_relative "nop"
2
+
3
+ module IRB
4
+ # :stopdoc:
5
+
6
+ module ExtendCommand
7
+ class Debug < Nop
8
+ BINDING_IRB_FRAME_REGEXPS = [
9
+ '<internal:prelude>',
10
+ binding.method(:irb).source_location.first,
11
+ ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ }
12
+ IRB_DIR = File.expand_path('..', __dir__)
13
+
14
+ def execute(*args)
15
+ unless binding_irb?
16
+ puts "`debug` command is only available when IRB is started with binding.irb"
17
+ return
18
+ end
19
+
20
+ unless setup_debugger
21
+ puts <<~MSG
22
+ You need to install the debug gem before using this command.
23
+ If you use `bundle exec`, please add `gem "debug"` into your Gemfile.
24
+ MSG
25
+ return
26
+ end
27
+
28
+ # To make debugger commands like `next` or `continue` work without asking
29
+ # the user to quit IRB after that, we need to exit IRB first and then hit
30
+ # a TracePoint on #debug_break.
31
+ file, lineno = IRB::Irb.instance_method(:debug_break).source_location
32
+ DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, oneshot: true, hook_call: false)
33
+ # exit current Irb#run call
34
+ throw :IRB_EXIT
35
+ end
36
+
37
+ private
38
+
39
+ def binding_irb?
40
+ caller.any? do |frame|
41
+ BINDING_IRB_FRAME_REGEXPS.any? do |regexp|
42
+ frame.match?(regexp)
43
+ end
44
+ end
45
+ end
46
+
47
+ def setup_debugger
48
+ unless defined?(DEBUGGER__::SESSION)
49
+ begin
50
+ require "debug/session"
51
+ rescue LoadError # debug.gem is not written in Gemfile
52
+ return false unless load_bundled_debug_gem
53
+ end
54
+ DEBUGGER__.start(nonstop: true)
55
+ end
56
+
57
+ unless DEBUGGER__.respond_to?(:capture_frames_without_irb)
58
+ DEBUGGER__.singleton_class.send(:alias_method, :capture_frames_without_irb, :capture_frames)
59
+
60
+ def DEBUGGER__.capture_frames(*args)
61
+ frames = capture_frames_without_irb(*args)
62
+ frames.reject! do |frame|
63
+ frame.realpath&.start_with?(IRB_DIR) || frame.path == "<internal:prelude>"
64
+ end
65
+ frames
66
+ end
67
+ end
68
+
69
+ true
70
+ end
71
+
72
+ # This is used when debug.gem is not written in Gemfile. Even if it's not
73
+ # installed by `bundle install`, debug.gem is installed by default because
74
+ # it's a bundled gem. This method tries to activate and load that.
75
+ def load_bundled_debug_gem
76
+ # Discover latest debug.gem under GEM_PATH
77
+ debug_gem = Gem.paths.path.map { |path| Dir.glob("#{path}/gems/debug-*") }.flatten.select do |path|
78
+ File.basename(path).match?(/\Adebug-\d+\.\d+\.\d+\z/)
79
+ end.sort_by do |path|
80
+ Gem::Version.new(File.basename(path).delete_prefix('debug-'))
81
+ end.last
82
+ return false unless debug_gem
83
+
84
+ # Attempt to forcibly load the bundled gem
85
+ $LOAD_PATH << "#{debug_gem}/lib"
86
+ begin
87
+ require "debug/session"
88
+ puts "Loaded #{File.basename(debug_gem)}"
89
+ true
90
+ rescue LoadError
91
+ false
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,65 @@
1
+ require 'shellwords'
2
+ require_relative "nop"
3
+
4
+ module IRB
5
+ # :stopdoc:
6
+
7
+ module ExtendCommand
8
+ class Edit < Nop
9
+ class << self
10
+ def transform_args(args)
11
+ # Return a string literal as is for backward compatibility
12
+ if args.nil? || args.empty? || string_literal?(args)
13
+ args
14
+ else # Otherwise, consider the input as a String for convenience
15
+ args.strip.dump
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def string_literal?(args)
22
+ sexp = Ripper.sexp(args)
23
+ sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
24
+ end
25
+ end
26
+
27
+ def execute(*args)
28
+ path = args.first
29
+
30
+ if path.nil? && (irb_path = @irb_context.irb_path)
31
+ path = irb_path
32
+ end
33
+
34
+ if !File.exist?(path)
35
+ require_relative "show_source"
36
+
37
+ source =
38
+ begin
39
+ ShowSource.find_source(path, @irb_context)
40
+ rescue NameError
41
+ # if user enters a path that doesn't exist, it'll cause NameError when passed here because find_source would try to evaluate it as well
42
+ # in this case, we should just ignore the error
43
+ end
44
+
45
+ if source && File.exist?(source.file)
46
+ path = source.file
47
+ else
48
+ puts "Can not find file: #{path}"
49
+ return
50
+ end
51
+ end
52
+
53
+ if editor = ENV['EDITOR']
54
+ puts "command: '#{editor}'"
55
+ puts " path: #{path}"
56
+ system(*Shellwords.split(editor), path)
57
+ else
58
+ puts "Can not find editor setting: ENV['EDITOR']"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # :startdoc:
65
+ end
@@ -19,8 +19,50 @@ module IRB
19
19
  end
20
20
  end
21
21
 
22
+ def find_source(str, irb_context)
23
+ case str
24
+ when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
25
+ eval(str, irb_context.workspace.binding) # trigger autoload
26
+ base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
27
+ file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
28
+ when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
29
+ owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
30
+ method = Regexp.last_match[:method]
31
+ if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
32
+ file, line = owner.instance_method(method).source_location
33
+ end
34
+ when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
35
+ receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
36
+ method = Regexp.last_match[:method]
37
+ file, line = receiver.method(method).source_location if receiver.respond_to?(method)
38
+ end
39
+ if file && line
40
+ Source.new(file: file, first_line: line, last_line: find_end(file, line))
41
+ end
42
+ end
43
+
22
44
  private
23
45
 
46
+ def find_end(file, first_line)
47
+ return first_line unless File.exist?(file)
48
+ lex = RubyLex.new
49
+ lines = File.read(file).lines[(first_line - 1)..-1]
50
+ tokens = RubyLex.ripper_lex_without_warning(lines.join)
51
+ prev_tokens = []
52
+
53
+ # chunk with line number
54
+ tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
55
+ code = lines[0..lnum].join
56
+ prev_tokens.concat chunk
57
+ continue = lex.process_continue(prev_tokens)
58
+ code_block_open = lex.check_code_block(code, prev_tokens)
59
+ if !continue && !code_block_open
60
+ return first_line + lnum
61
+ end
62
+ end
63
+ first_line
64
+ end
65
+
24
66
  def string_literal?(args)
25
67
  sexp = Ripper.sexp(args)
26
68
  sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal
@@ -32,7 +74,8 @@ module IRB
32
74
  puts "Error: Expected a string but got #{str.inspect}"
33
75
  return
34
76
  end
35
- source = find_source(str)
77
+
78
+ source = self.class.find_source(str, @irb_context)
36
79
  if source && File.exist?(source.file)
37
80
  show_source(source)
38
81
  else
@@ -53,48 +96,6 @@ module IRB
53
96
  puts
54
97
  end
55
98
 
56
- def find_source(str)
57
- case str
58
- when /\A[A-Z]\w*(::[A-Z]\w*)*\z/ # Const::Name
59
- eval(str, irb_context.workspace.binding) # trigger autoload
60
- base = irb_context.workspace.binding.receiver.yield_self { |r| r.is_a?(Module) ? r : Object }
61
- file, line = base.const_source_location(str) if base.respond_to?(:const_source_location) # Ruby 2.7+
62
- when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method
63
- owner = eval(Regexp.last_match[:owner], irb_context.workspace.binding)
64
- method = Regexp.last_match[:method]
65
- if owner.respond_to?(:instance_method) && owner.instance_methods.include?(method.to_sym)
66
- file, line = owner.instance_method(method).source_location
67
- end
68
- when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method
69
- receiver = eval(Regexp.last_match[:receiver] || 'self', irb_context.workspace.binding)
70
- method = Regexp.last_match[:method]
71
- file, line = receiver.method(method).source_location if receiver.respond_to?(method)
72
- end
73
- if file && line
74
- Source.new(file: file, first_line: line, last_line: find_end(file, line))
75
- end
76
- end
77
-
78
- def find_end(file, first_line)
79
- return first_line unless File.exist?(file)
80
- lex = RubyLex.new
81
- lines = File.read(file).lines[(first_line - 1)..-1]
82
- tokens = RubyLex.ripper_lex_without_warning(lines.join)
83
- prev_tokens = []
84
-
85
- # chunk with line number
86
- tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk|
87
- code = lines[0..lnum].join
88
- prev_tokens.concat chunk
89
- continue = lex.process_continue(prev_tokens)
90
- code_block_open = lex.check_code_block(code, prev_tokens)
91
- if !continue && !code_block_open
92
- return first_line + lnum
93
- end
94
- end
95
- first_line
96
- end
97
-
98
99
  def bold(str)
99
100
  Color.colorize(str, [:BOLD])
100
101
  end
@@ -116,6 +116,14 @@ module IRB # :nodoc:
116
116
  [:kill, OVERRIDE_PRIVATE_ONLY],
117
117
  ],
118
118
 
119
+ [
120
+ :irb_debug, :Debug, "cmd/debug",
121
+ [:debug, NO_OVERRIDE],
122
+ ],
123
+ [
124
+ :irb_edit, :Edit, "cmd/edit",
125
+ [:edit, NO_OVERRIDE],
126
+ ],
119
127
  [
120
128
  :irb_help, :Help, "cmd/help",
121
129
  [:help, NO_OVERRIDE],
@@ -161,26 +169,7 @@ module IRB # :nodoc:
161
169
  nil
162
170
  end
163
171
 
164
- # Installs the default irb commands:
165
- #
166
- # +irb_current_working_workspace+:: Context#main
167
- # +irb_change_workspace+:: Context#change_workspace
168
- # +irb_workspaces+:: Context#workspaces
169
- # +irb_push_workspace+:: Context#push_workspace
170
- # +irb_pop_workspace+:: Context#pop_workspace
171
- # +irb_load+:: #irb_load
172
- # +irb_require+:: #irb_require
173
- # +irb_source+:: IrbLoader#source_file
174
- # +irb+:: IRB.irb
175
- # +irb_jobs+:: JobManager
176
- # +irb_fg+:: JobManager#switch
177
- # +irb_kill+:: JobManager#kill
178
- # +irb_help+:: IRB@Command+line+options
179
- # +irb_info+:: #inspect
180
- # +irb_ls+:: Output#dump
181
- # +irb_measure+:: IRB::unset_measure_callback
182
- # +irb_show_source+:: #find_source, #show_source
183
- # +irb_whereami+:: Workspace#code_around_binding
172
+ # Installs the default irb commands.
184
173
  def self.install_extend_commands
185
174
  for args in @EXTEND_COMMANDS
186
175
  def_extend_command(*args)
data/lib/irb/version.rb CHANGED
@@ -11,7 +11,7 @@
11
11
  #
12
12
 
13
13
  module IRB # :nodoc:
14
- VERSION = "1.4.3"
14
+ VERSION = "1.5.0"
15
15
  @RELEASE_VERSION = VERSION
16
- @LAST_UPDATE_DATE = "2022-11-17"
16
+ @LAST_UPDATE_DATE = "2022-11-20"
17
17
  end
data/lib/irb.rb CHANGED
@@ -53,6 +53,50 @@ require_relative "irb/easter-egg"
53
53
  #
54
54
  # :include: ./irb/lc/help-message
55
55
  #
56
+ # == Commands
57
+ #
58
+ # The following commands are available on IRB.
59
+ #
60
+ # * cwws
61
+ # * Show the current workspace.
62
+ # * cb, cws, chws
63
+ # * Change the current workspace to an object.
64
+ # * bindings, workspaces
65
+ # * Show workspaces.
66
+ # * pushb, pushws
67
+ # * Push an object to the workspace stack.
68
+ # * popb, popws
69
+ # * Pop a workspace from the workspace stack.
70
+ # * load
71
+ # * Load a Ruby file.
72
+ # * require
73
+ # * Require a Ruby file.
74
+ # * source
75
+ # * Loads a given file in the current session.
76
+ # * irb
77
+ # * Start a child IRB.
78
+ # * jobs
79
+ # * List of current sessions.
80
+ # * fg
81
+ # * Switches to the session of the given number.
82
+ # * kill
83
+ # * Kills the session with the given number.
84
+ # * help
85
+ # * Enter the mode to look up RI documents.
86
+ # * irb_info
87
+ # * Show information about IRB.
88
+ # * ls
89
+ # * Show methods, constants, and variables.
90
+ # -g [query] or -G [query] allows you to filter out the output.
91
+ # * measure
92
+ # * measure enables the mode to measure processing time. measure :off disables it.
93
+ # * $, show_source
94
+ # * Show the source code of a given method or constant.
95
+ # * @, whereami
96
+ # * Show the source code around binding.irb again.
97
+ # * debug
98
+ # * Start the debugger of debug.gem.
99
+ #
56
100
  # == Configuration
57
101
  #
58
102
  # IRB reads a personal initialization file when it's invoked.
@@ -93,9 +137,9 @@ require_relative "irb/easter-egg"
93
137
  #
94
138
  # === Autocompletion
95
139
  #
96
- # To enable autocompletion for irb, add the following to your +.irbrc+:
140
+ # To disable autocompletion for irb, add the following to your +.irbrc+:
97
141
  #
98
- # require 'irb/completion'
142
+ # IRB.conf[:USE_AUTOCOMPLETE] = false
99
143
  #
100
144
  # === History
101
145
  #
@@ -434,6 +478,17 @@ module IRB
434
478
  @scanner = RubyLex.new
435
479
  end
436
480
 
481
+ # A hook point for `debug` command's TracePoint after :IRB_EXIT as well as its clean-up
482
+ def debug_break
483
+ # it means the debug command is executed
484
+ if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb)
485
+ # after leaving this initial breakpoint, revert the capture_frames patch
486
+ DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb)
487
+ # and remove the redundant method
488
+ DEBUGGER__.singleton_class.send(:undef_method, :capture_frames_without_irb)
489
+ end
490
+ end
491
+
437
492
  def run(conf = IRB.conf)
438
493
  conf[:IRB_RC].call(context) if conf[:IRB_RC]
439
494
  conf[:MAIN_CONTEXT] = context
@@ -924,12 +979,13 @@ class Binding
924
979
  #
925
980
  #
926
981
  # See IRB@IRB+Usage for more information.
927
- def irb
982
+ def irb(show_code: true)
928
983
  IRB.setup(source_location[0], argv: [])
929
984
  workspace = IRB::WorkSpace.new(self)
930
- STDOUT.print(workspace.code_around_binding)
985
+ STDOUT.print(workspace.code_around_binding) if show_code
931
986
  binding_irb = IRB::Irb.new(workspace)
932
987
  binding_irb.context.irb_path = File.expand_path(source_location[0])
933
988
  binding_irb.run(IRB.conf)
989
+ binding_irb.debug_break
934
990
  end
935
991
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: irb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.3
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  - Keiju ISHITSUKA
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-11-17 00:00:00.000000000 Z
12
+ date: 2022-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: reline
@@ -47,6 +47,8 @@ files:
47
47
  - irb.gemspec
48
48
  - lib/irb.rb
49
49
  - lib/irb/cmd/chws.rb
50
+ - lib/irb/cmd/debug.rb
51
+ - lib/irb/cmd/edit.rb
50
52
  - lib/irb/cmd/fork.rb
51
53
  - lib/irb/cmd/help.rb
52
54
  - lib/irb/cmd/info.rb
@@ -99,7 +101,7 @@ licenses:
99
101
  - Ruby
100
102
  - BSD-2-Clause
101
103
  metadata: {}
102
- post_install_message:
104
+ post_install_message:
103
105
  rdoc_options: []
104
106
  require_paths:
105
107
  - lib
@@ -114,8 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
116
  - !ruby/object:Gem::Version
115
117
  version: '0'
116
118
  requirements: []
117
- rubygems_version: 3.4.0.dev
118
- signing_key:
119
+ rubygems_version: 3.3.7
120
+ signing_key:
119
121
  specification_version: 4
120
122
  summary: Interactive Ruby command-line tool for REPL (Read Eval Print Loop).
121
123
  test_files: []