byebug 8.2.5 → 9.1.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/CONTRIBUTING.md +6 -6
  4. data/GUIDE.md +26 -26
  5. data/README.md +18 -7
  6. data/bin/byebug +3 -1
  7. data/ext/byebug/breakpoint.c +3 -3
  8. data/ext/byebug/byebug.c +7 -4
  9. data/ext/byebug/byebug.h +0 -4
  10. data/ext/byebug/locker.c +1 -1
  11. data/ext/byebug/threads.c +2 -2
  12. data/lib/byebug/breakpoint.rb +2 -4
  13. data/lib/byebug/commands/catch.rb +1 -1
  14. data/lib/byebug/commands/delete.rb +4 -2
  15. data/lib/byebug/commands/edit.rb +1 -1
  16. data/lib/byebug/commands/enable/breakpoints.rb +1 -1
  17. data/lib/byebug/commands/finish.rb +1 -1
  18. data/lib/byebug/commands/info/breakpoints.rb +1 -1
  19. data/lib/byebug/commands/info/file.rb +2 -3
  20. data/lib/byebug/commands/irb.rb +8 -6
  21. data/lib/byebug/commands/kill.rb +1 -1
  22. data/lib/byebug/commands/pry.rb +3 -3
  23. data/lib/byebug/commands/restart.rb +22 -5
  24. data/lib/byebug/commands/save.rb +1 -1
  25. data/lib/byebug/core.rb +11 -4
  26. data/lib/byebug/frame.rb +6 -6
  27. data/lib/byebug/helpers/bin.rb +26 -0
  28. data/lib/byebug/helpers/file.rb +3 -3
  29. data/lib/byebug/helpers/parse.rb +1 -1
  30. data/lib/byebug/helpers/path.rb +7 -11
  31. data/lib/byebug/helpers/string.rb +11 -1
  32. data/lib/byebug/helpers/thread.rb +7 -5
  33. data/lib/byebug/helpers/toggle.rb +25 -24
  34. data/lib/byebug/helpers/var.rb +1 -1
  35. data/lib/byebug/history.rb +9 -2
  36. data/lib/byebug/interface.rb +3 -0
  37. data/lib/byebug/interfaces/local_interface.rb +5 -6
  38. data/lib/byebug/interfaces/script_interface.rb +4 -3
  39. data/lib/byebug/printers/base.rb +2 -1
  40. data/lib/byebug/printers/plain.rb +1 -2
  41. data/lib/byebug/printers/texts/base.yml +4 -0
  42. data/lib/byebug/printers/texts/plain.yml +1 -1
  43. data/lib/byebug/processors/command_processor.rb +15 -15
  44. data/lib/byebug/processors/script_processor.rb +6 -12
  45. data/lib/byebug/remote.rb +19 -19
  46. data/lib/byebug/runner.rb +65 -56
  47. data/lib/byebug/setting.rb +1 -1
  48. data/lib/byebug/settings/callstyle.rb +1 -0
  49. data/lib/byebug/version.rb +2 -1
  50. metadata +12 -13
data/lib/byebug/core.rb CHANGED
@@ -47,13 +47,11 @@ module Byebug
47
47
  # are debugging, in the directory where you invoke byebug.
48
48
  #
49
49
  def run_init_script
50
- run_rc_file(ENV['HOME'])
51
-
52
- run_rc_file(Dir.pwd) unless Dir.pwd == ENV['HOME']
50
+ rc_dirs.each { |dir| run_rc_file(dir) }
53
51
  end
54
52
 
55
53
  def self.load_settings
56
- Dir.glob(File.expand_path('../settings/*.rb', __FILE__)).each do |file|
54
+ Dir.glob(File.join(__dir__, 'settings', '*.rb')).each do |file|
57
55
  require file
58
56
  end
59
57
 
@@ -94,6 +92,15 @@ module Byebug
94
92
  ensure
95
93
  Context.interface = old_interface
96
94
  end
95
+
96
+ #
97
+ # List of folders to load rc files from
98
+ #
99
+ # @note Files will be loaded in the order specified here.
100
+ #
101
+ def rc_dirs
102
+ [ENV['HOME'], Dir.pwd].compact.uniq
103
+ end
97
104
  end
98
105
 
99
106
  Byebug.load_settings
data/lib/byebug/frame.rb CHANGED
@@ -47,13 +47,13 @@ module Byebug
47
47
  #
48
48
  # Gets local variables for the frame.
49
49
  #
50
- # TODO: Use brand new local_variable_get, local_variable_set and
51
- # local_variable defined for rubies >= 2.1
52
- #
53
50
  def locals
54
51
  return [] unless _binding
55
52
 
56
- _binding.eval('local_variables.inject({}){|h, v| h[v] = eval(v.to_s); h}')
53
+ _binding.local_variables.each_with_object({}) do |e, a|
54
+ a[e] = _binding.local_variable_get(e)
55
+ a
56
+ end
57
57
  end
58
58
 
59
59
  #
@@ -158,14 +158,14 @@ module Byebug
158
158
  def c_args
159
159
  return [] unless _self.to_s != 'main'
160
160
 
161
- _self.method(_method).parameters
161
+ _class.instance_method(_method).parameters
162
162
  end
163
163
 
164
164
  def ruby_args
165
165
  meth_name = _binding.eval('__method__')
166
166
  return [] unless meth_name
167
167
 
168
- meth_obj = _self.method(meth_name)
168
+ meth_obj = _class.instance_method(meth_name)
169
169
  return [] unless meth_obj
170
170
 
171
171
  meth_obj.parameters
@@ -0,0 +1,26 @@
1
+ module Byebug
2
+ module Helpers
3
+ #
4
+ # Utilities for interaction with executables
5
+ #
6
+ module BinHelper
7
+ #
8
+ # Cross-platform way of finding an executable in the $PATH.
9
+ # Borrowed from: http://stackoverflow.com/questions/2108727
10
+ #
11
+ def which(cmd)
12
+ return File.expand_path(cmd) if File.exist?(cmd)
13
+
14
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
15
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
16
+ exts.each do |ext|
17
+ exe = File.join(path, "#{cmd}#{ext}")
18
+ return exe if File.executable?(exe) && !File.directory?(exe)
19
+ end
20
+ end
21
+
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -8,7 +8,7 @@ module Byebug
8
8
  # Reads lines of source file +filename+ into an array
9
9
  #
10
10
  def get_lines(filename)
11
- File.foreach(filename).reduce([]) { |a, e| a << e.chomp }
11
+ File.foreach(filename).reduce([]) { |acc, elem| acc << elem.chomp }
12
12
  end
13
13
 
14
14
  #
@@ -26,7 +26,7 @@ module Byebug
26
26
  # one-line-at-a-time way.
27
27
  #
28
28
  def n_lines(filename)
29
- File.foreach(filename).reduce(0) { |a, _e| a + 1 }
29
+ File.foreach(filename).reduce(0) { |acc, _elem| acc + 1 }
30
30
  end
31
31
 
32
32
  #
@@ -54,7 +54,7 @@ module Byebug
54
54
  # True for special files like -e, false otherwise
55
55
  #
56
56
  def virtual_file?(name)
57
- ['(irb)', '-e', '(byebug)'].include?(name)
57
+ ['(irb)', '-e', '(byebug)', '(eval)'].include?(name)
58
58
  end
59
59
  end
60
60
  end
@@ -9,7 +9,7 @@ module Byebug
9
9
  #
10
10
  # If either +min+ or +max+ is nil, that value has no bound.
11
11
  #
12
- # TODO: Remove the `cmd` parameter. It has nothing to do with the method's
12
+ # @todo Remove the `cmd` parameter. It has nothing to do with the method's
13
13
  # purpose.
14
14
  #
15
15
  def get_int(str, cmd, min = nil, max = nil)
@@ -5,27 +5,23 @@ module Byebug
5
5
  #
6
6
  module PathHelper
7
7
  def bin_file
8
- @bin_file ||= Gem.bin_path('byebug', 'byebug')
8
+ @bin_file ||= File.join(root_path, 'bin', 'byebug')
9
9
  end
10
10
 
11
11
  def root_path
12
- @root_path ||= File.expand_path('../..', bin_file)
12
+ @root_path ||= File.expand_path(File.join('..', '..', '..'), __dir__)
13
13
  end
14
14
 
15
15
  def lib_files
16
- @lib_files ||= expand_from_root('lib/**/*.{rb,yml}')
17
- end
18
-
19
- def ext_files
20
- @ext_files ||= expand_from_root('ext/**/*.{c,h,rb}')
16
+ @lib_files ||= glob_for('lib')
21
17
  end
22
18
 
23
19
  def test_files
24
- @test_files ||= expand_from_root('test/**/*.rb')
20
+ @test_files ||= glob_for('test')
25
21
  end
26
22
 
27
23
  def gem_files
28
- @gem_files ||= [bin_file] + lib_files + ext_files
24
+ @gem_files ||= [bin_file] + lib_files
29
25
  end
30
26
 
31
27
  def all_files
@@ -34,8 +30,8 @@ module Byebug
34
30
 
35
31
  private
36
32
 
37
- def expand_from_root(glob)
38
- Dir.glob(File.expand_path(glob, root_path))
33
+ def glob_for(dir)
34
+ Dir.glob(File.join(root_path, dir, '**', '*.rb'))
39
35
  end
40
36
  end
41
37
  end
@@ -17,7 +17,17 @@ module Byebug
17
17
  # command prompt.
18
18
  #
19
19
  def prettify(str)
20
- "\n" + str.gsub(/^ {6}/, '') + "\n"
20
+ "\n" + deindent(str) + "\n"
21
+ end
22
+
23
+ #
24
+ # Removes a number of leading whitespace for each input line.
25
+ #
26
+ # @note Might be unnecessary when Ruby 2.2 support is dropped and we can
27
+ # use squiggly heredoc's.
28
+ #
29
+ def deindent(str, leading_spaces: 6)
30
+ str.gsub(/^ {#{leading_spaces}}/, '')
21
31
  end
22
32
  end
23
33
  end
@@ -28,10 +28,12 @@ module Byebug
28
28
  def context_from_thread(thnum)
29
29
  ctx = Byebug.contexts.find { |c| c.thnum.to_s == thnum }
30
30
 
31
- err = case
32
- when ctx.nil? then pr('thread.errors.no_thread')
33
- when ctx == context then pr('thread.errors.current_thread')
34
- when ctx.ignored? then pr('thread.errors.ignored', arg: thnum)
31
+ err = if ctx.nil?
32
+ pr('thread.errors.no_thread')
33
+ elsif ctx == context
34
+ pr('thread.errors.current_thread')
35
+ elsif ctx.ignored?
36
+ pr('thread.errors.ignored', arg: thnum)
35
37
  end
36
38
 
37
39
  [ctx, err]
@@ -39,7 +41,7 @@ module Byebug
39
41
 
40
42
  private
41
43
 
42
- # TODO: Check whether it is Byebug.current_context or context
44
+ # @todo Check whether it is Byebug.current_context or context
43
45
  def location(ctx)
44
46
  return context.location if ctx == Byebug.current_context
45
47
 
@@ -9,50 +9,51 @@ module Byebug
9
9
  include ParseHelper
10
10
 
11
11
  def enable_disable_breakpoints(is_enable, args)
12
- return errmsg(pr('toggle.errors.no_breakpoints')) if Breakpoint.none?
12
+ raise pr('toggle.errors.no_breakpoints') if Breakpoint.none?
13
13
 
14
- all_breakpoints = Byebug.breakpoints.sort_by(&:id)
15
- if args.nil?
16
- selected_breakpoints = all_breakpoints
17
- else
18
- selected_ids = []
19
- args.split(/ +/).each do |pos|
20
- last_id = all_breakpoints.last.id
21
- pos, err = get_int(pos, "#{is_enable} breakpoints", 1, last_id)
22
- return errmsg(err) unless pos
23
-
24
- selected_ids << pos
25
- end
26
- selected_breakpoints = all_breakpoints.select do |b|
27
- selected_ids.include?(b.id)
28
- end
29
- end
30
-
31
- selected_breakpoints.each do |b|
32
- enabled = ('enable' == is_enable)
14
+ select_breakpoints(is_enable, args).each do |b|
15
+ enabled = (is_enable == 'enable')
33
16
  if enabled && !syntax_valid?(b.expr)
34
- return errmsg(pr('toggle.errors.expression', expr: b.expr))
17
+ raise pr('toggle.errors.expression', expr: b.expr)
35
18
  end
36
19
 
20
+ puts pr('toggle.messages.toggled', bpnum: b.id,
21
+ endis: enabled ? 'en' : 'dis')
37
22
  b.enabled = enabled
38
23
  end
39
24
  end
40
25
 
41
26
  def enable_disable_display(is_enable, args)
42
- return errmsg(pr('toggle.errors.no_display')) if 0 == n_displays
27
+ raise pr('toggle.errors.no_display') if n_displays.zero?
43
28
 
44
29
  selected_displays = args ? args.split(/ +/) : [1..n_displays + 1]
45
30
 
46
31
  selected_displays.each do |pos|
47
32
  pos, err = get_int(pos, "#{is_enable} display", 1, n_displays)
48
- return errmsg(err) unless err.nil?
33
+ raise err unless err.nil?
49
34
 
50
- Byebug.displays[pos - 1][0] = ('enable' == is_enable)
35
+ Byebug.displays[pos - 1][0] = (is_enable == 'enable')
51
36
  end
52
37
  end
53
38
 
54
39
  private
55
40
 
41
+ def select_breakpoints(is_enable, args)
42
+ all_breakpoints = Byebug.breakpoints.sort_by(&:id)
43
+ return all_breakpoints if args.nil?
44
+
45
+ selected_ids = []
46
+ args.split(/ +/).each do |pos|
47
+ last_id = all_breakpoints.last.id
48
+ pos, err = get_int(pos, "#{is_enable} breakpoints", 1, last_id)
49
+ raise(ArgumentError, err) unless pos
50
+
51
+ selected_ids << pos
52
+ end
53
+
54
+ all_breakpoints.select { |b| selected_ids.include?(b.id) }
55
+ end
56
+
56
57
  def n_displays
57
58
  Byebug.displays.size
58
59
  end
@@ -18,7 +18,7 @@ module Byebug
18
18
 
19
19
  def var_global
20
20
  globals = global_variables.reject do |v|
21
- [:$IGNORECASE, :$=, :$KCODE, :$-K, :$binding].include?(v)
21
+ %i[$IGNORECASE $= $KCODE $-K $binding].include?(v)
22
22
  end
23
23
 
24
24
  var_list(globals)
@@ -21,6 +21,13 @@ module Byebug
21
21
  self.size = 0
22
22
  end
23
23
 
24
+ #
25
+ # Array holding the list of commands in history
26
+ #
27
+ def buffer
28
+ Readline::HISTORY.to_a
29
+ end
30
+
24
31
  #
25
32
  # Restores history from disk.
26
33
  #
@@ -74,7 +81,7 @@ module Byebug
74
81
  def to_s(n_cmds)
75
82
  show_size = n_cmds ? specific_max_size(n_cmds) : default_max_size
76
83
 
77
- commands = Readline::HISTORY.to_a.last(show_size)
84
+ commands = buffer.last(show_size)
78
85
 
79
86
  last_ids(show_size).zip(commands).map do |l|
80
87
  format('%5d %s', l[0], l[1])
@@ -115,7 +122,7 @@ module Byebug
115
122
  return true if /^\s*$/ =~ buf
116
123
  return false if Readline::HISTORY.empty?
117
124
 
118
- Readline::HISTORY[Readline::HISTORY.length - 1] == buf
125
+ buffer[Readline::HISTORY.length - 1] == buf
119
126
  end
120
127
  end
121
128
  end
@@ -86,6 +86,9 @@ module Byebug
86
86
  output.puts(message)
87
87
  end
88
88
 
89
+ #
90
+ # Prints an output message to the output stream without a final "\n".
91
+ #
89
92
  def print(message)
90
93
  output.print(message)
91
94
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Byebug
3
4
  #
4
5
  # Interface class for standard byebug use.
@@ -8,9 +9,9 @@ module Byebug
8
9
 
9
10
  def initialize
10
11
  super()
11
- @input = STDIN
12
- @output = STDOUT
13
- @error = STDERR
12
+ @input = $stdin
13
+ @output = $stdout
14
+ @error = $stderr
14
15
  end
15
16
 
16
17
  #
@@ -20,9 +21,7 @@ module Byebug
20
21
  # @param prompt Prompt to be displayed.
21
22
  #
22
23
  def readline(prompt)
23
- with_repl_like_sigint do
24
- Readline.readline(prompt, false) || EOF_ALIAS
25
- end
24
+ with_repl_like_sigint { Readline.readline(prompt) || EOF_ALIAS }
26
25
  end
27
26
 
28
27
  #
@@ -5,9 +5,10 @@ module Byebug
5
5
  class ScriptInterface < Interface
6
6
  def initialize(file, verbose = false)
7
7
  super()
8
+ @verbose = verbose
8
9
  @input = File.open(file)
9
- @output = verbose ? STDOUT : StringIO.new
10
- @error = verbose ? STDERR : StringIO.new
10
+ @output = verbose ? $stdout : StringIO.new
11
+ @error = $stderr
11
12
  end
12
13
 
13
14
  def read_command(prompt)
@@ -20,7 +21,7 @@ module Byebug
20
21
 
21
22
  def readline(*)
22
23
  while (result = input.gets)
23
- output.puts "+ #{result}"
24
+ output.puts "+ #{result}" if @verbose
24
25
  next if result =~ /^\s*#/
25
26
  return result.chomp
26
27
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'yaml'
3
4
 
4
5
  module Byebug
@@ -61,7 +62,7 @@ module Byebug
61
62
  end
62
63
 
63
64
  def contents_files
64
- [File.expand_path(File.join('..', 'texts', 'base.yml'), __FILE__)]
65
+ [File.join(__dir__, 'texts', 'base.yml')]
65
66
  end
66
67
  end
67
68
  end
@@ -35,8 +35,7 @@ module Byebug
35
35
  private
36
36
 
37
37
  def contents_files
38
- [File.expand_path(File.join('..', 'texts', 'plain.yml'), __FILE__)] +
39
- super
38
+ [File.join(__dir__, 'texts', 'plain.yml')] + super
40
39
  end
41
40
  end
42
41
  end
@@ -16,6 +16,8 @@ break:
16
16
  not_changed: "Incorrect expression \"{expr}\", breakpoint not changed"
17
17
  confirmations:
18
18
  delete_all: "Delete all breakpoints?"
19
+ messages:
20
+ breakpoint_deleted: "Deleted breakpoint {pos}"
19
21
 
20
22
  catch:
21
23
  added: "Catching exception {exception}."
@@ -97,6 +99,8 @@ toggle:
97
99
  no_display: "No display expressions have been set"
98
100
  syntax: "\"{toggle}\" must be followed by \"display\", \"breakpoints\" or breakpoint ids"
99
101
  expression: "Expression \"{expr}\" syntactically incorrect; breakpoint remains disabled."
102
+ messages:
103
+ toggled: "Breakpoint {bpnum} {endis}abled"
100
104
 
101
105
  parse:
102
106
  errors:
@@ -1,5 +1,5 @@
1
1
  break:
2
- created: "Successfully created breakpoint with id {id}"
2
+ created: "Created breakpoint {id} at {file}:{line}"
3
3
 
4
4
  display:
5
5
  result: "{n}: {exp} = {result}"
@@ -109,21 +109,6 @@ module Byebug
109
109
  '(byebug) '
110
110
  end
111
111
 
112
- private
113
-
114
- def auto_cmds_for(run_level)
115
- command_list.select { |cmd| cmd.always_run >= run_level }
116
- end
117
-
118
- #
119
- # Run permanent commands.
120
- #
121
- def run_auto_cmds(run_level)
122
- safely do
123
- auto_cmds_for(run_level).each { |cmd| cmd.new(self).execute }
124
- end
125
- end
126
-
127
112
  def before_repl
128
113
  @proceed = false
129
114
  @prev_line = nil
@@ -150,6 +135,21 @@ module Byebug
150
135
  end
151
136
  end
152
137
 
138
+ private
139
+
140
+ def auto_cmds_for(run_level)
141
+ command_list.select { |cmd| cmd.always_run >= run_level }
142
+ end
143
+
144
+ #
145
+ # Run permanent commands.
146
+ #
147
+ def run_auto_cmds(run_level)
148
+ safely do
149
+ auto_cmds_for(run_level).each { |cmd| cmd.new(self).execute }
150
+ end
151
+ end
152
+
153
153
  #
154
154
  # Executes the received input
155
155
  #
@@ -12,25 +12,19 @@ module Byebug
12
12
  super.select(&:allow_in_control)
13
13
  end
14
14
 
15
- def process_commands
15
+ def repl
16
16
  while (input = interface.read_command(prompt))
17
- command = command_list.match(input)
17
+ safely do
18
+ command = command_list.match(input)
19
+ raise CommandNotFound.new(input) unless command
18
20
 
19
- if command
20
21
  command.new(self, input).execute
21
- else
22
- errmsg('Unknown command')
23
22
  end
24
23
  end
24
+ end
25
25
 
26
+ def after_repl
26
27
  interface.close
27
- rescue IOError, SystemCallError
28
- interface.close
29
- rescue
30
- without_exceptions do
31
- puts "INTERNAL ERROR!!! #{$ERROR_INFO}"
32
- puts $ERROR_INFO.backtrace.map { |l| " #{l}" }.join("\n")
33
- end
34
28
  end
35
29
 
36
30
  #
data/lib/byebug/remote.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  require 'socket'
2
+ require 'byebug/processors/control_processor'
2
3
 
3
4
  #
4
5
  # Remote debugging functionality.
5
6
  #
6
- # TODO: Refactor & add tests
7
+ # @todo Refactor & add tests
7
8
  #
8
9
  module Byebug
9
10
  # Port number used for remote debugging
@@ -33,9 +34,7 @@ module Byebug
33
34
  Context.interface = nil
34
35
  start
35
36
 
36
- start_control(host, port == 0 ? 0 : port + 1)
37
-
38
- yield if block_given?
37
+ start_control(host, port.zero? ? 0 : port + 1)
39
38
 
40
39
  mutex = Mutex.new
41
40
  proceed = ConditionVariable.new
@@ -43,6 +42,8 @@ module Byebug
43
42
  server = TCPServer.new(host, port)
44
43
  self.actual_port = server.addr[1]
45
44
 
45
+ yield if block_given?
46
+
46
47
  @thread = DebugThread.new do
47
48
  while (session = server.accept)
48
49
  Context.interface = RemoteInterface.new(session)
@@ -73,27 +74,26 @@ module Byebug
73
74
  # Connects to the remote byebug
74
75
  #
75
76
  def start_client(host = 'localhost', port = PORT)
76
- Context.interface = LocalInterface.new
77
+ interface = LocalInterface.new
77
78
  puts 'Connecting to byebug server...'
78
79
  socket = TCPSocket.new(host, port)
79
80
  puts 'Connected.'
80
81
 
81
- catch(:exit) do
82
- while (line = socket.gets)
83
- case line
84
- when /^PROMPT (.*)$/
85
- input = Context.interface.read_command(Regexp.last_match[1])
86
- throw :exit unless input
87
- socket.puts input
88
- when /^CONFIRM (.*)$/
89
- input = Context.interface.confirm(Regexp.last_match[1])
90
- throw :exit unless input
91
- socket.puts input
92
- else
93
- puts line
94
- end
82
+ while (line = socket.gets)
83
+ case line
84
+ when /^PROMPT (.*)$/
85
+ input = interface.read_command(Regexp.last_match[1])
86
+ break unless input
87
+ socket.puts input
88
+ when /^CONFIRM (.*)$/
89
+ input = interface.readline(Regexp.last_match[1])
90
+ break unless input
91
+ socket.puts input
92
+ else
93
+ puts line
95
94
  end
96
95
  end
96
+
97
97
  socket.close
98
98
  end
99
99