gubed 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 208a0c9f02fb1d518500ba12917be9aa63454f8a1f270a25385b22479a70f29f
4
+ data.tar.gz: 501f3302b575817e1273decf824d6a640974ddbe43a5471256595f5f27138fd5
5
+ SHA512:
6
+ metadata.gz: 1ec8528a5c4795934aff69083d308eb8e906b9d69009250e26f96c3892092043d4ff5dbee1231040c0085e542b0eca2f2f678bdae8e9ee9e407ce949c239953b
7
+ data.tar.gz: fbe63215e825481e7471a26353034991a4ca9c78a8b8a83f187dfd1bc479505522124fb50eff71839b0f44b258893afee7480c85d75c7d0297738d80d87f451b
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-08-05
4
+
5
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Thomas Countz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Thomas Countz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Gubed
2
+
3
+ A command-line tool for managing debugger breakpoints in Ruby projects. Find, comment, uncomment, and delete debugging statements like `binding.pry`, `debugger`, `binding.irb`, and more.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install gubed
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Run `gubed` in any Ruby project directory to scan for debugging breakpoints:
14
+
15
+ ```bash
16
+ $ gubed --help
17
+
18
+ gubed # Scan current directory
19
+ gubed /path/to/project # Scan specific directory
20
+ ```
21
+
22
+ ### Interactive Mode
23
+
24
+ Once Gubed finds breakpoints, you can:
25
+
26
+ - Navigate with `j` (down) and `k` (up)
27
+ - Press `c` to comment/uncomment breakpoints
28
+ - Press `d` to delete breakpoints
29
+ - Press `v` to view surrounding code context
30
+ - Press `q` to quit
31
+ - Press `h` for help
32
+
33
+ ### Supported Breakpoint Types
34
+
35
+ These are found based on regex.
36
+
37
+ - `binding.pry`
38
+ - `binding.irb`
39
+ - `binding.break`
40
+ - `debugger`
41
+ - `byebug`
42
+
43
+ ## Example
44
+
45
+ ```bash
46
+ $ gubed
47
+ Gubed - Ruby Breakpoint Manager
48
+ ========================================
49
+
50
+ > 1. [#] binding.irb /Users/thomas.countz/Code/experiments/gubed/examples/calculator.rb:14
51
+ 2. [ ] binding.break /Users/thomas.countz/Code/experiments/gubed/examples/calculator.rb:19
52
+ 3. [ ] binding.pry /Users/thomas.countz/Code/experiments/gubed/examples/calculator.rb:3
53
+ 4. [#] debugger /Users/thomas.countz/Code/experiments/gubed/examples/calculator.rb:8
54
+ 5. [#] debugger /Users/thomas.countz/Code/experiments/gubed/examples/sample_app.rb:11
55
+ 6. [ ] debugger /Users/thomas.countz/Code/experiments/gubed/examples/sample_app.rb:15
56
+ 7. [#] binding.break /Users/thomas.countz/Code/experiments/gubed/examples/sample_app.rb:17
57
+ 8. [ ] binding.break /Users/thomas.countz/Code/experiments/gubed/examples/sample_app.rb:22
58
+ 9. [ ] byebug /Users/thomas.countz/Code/experiments/gubed/examples/sample_app.rb:25
59
+ 10. [ ] binding.pry /Users/thomas.countz/Code/experiments/gubed/examples/sample_app.rb:4
60
+ 11. [#] binding.irb /Users/thomas.countz/Code/experiments/gubed/examples/sample_app.rb:9
61
+
62
+ Commands: [j]down [k]up [g]oto [v]iew [t]oggle [d]elete [r]efresh [q]uit [h]elp
63
+ Selected: 1 of 11
64
+ ```
65
+
66
+ ## Development
67
+
68
+ After checking out the repo, run `bin/setup` to install dependencies. Run `rake test` to run the tests.
69
+
70
+ ## License
71
+
72
+ MIT License Copyright (c) 2025 Thomas Countz
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[test standard]
@@ -0,0 +1,22 @@
1
+ class Calculator
2
+ def add(a, b)
3
+ binding.pry
4
+ a + b
5
+ end
6
+
7
+ def subtract(a, b)
8
+ # debugger
9
+ a - b
10
+ end
11
+
12
+ def multiply(a, b)
13
+ a * b
14
+ # binding.irb
15
+ end
16
+
17
+ def divide(a, b)
18
+ return 0 if b == 0
19
+ binding.break
20
+ a / b
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ class SampleApp
2
+ def initialize
3
+ @users = []
4
+ binding.pry # Debug initialization
5
+ end
6
+
7
+ def add_user(name, email)
8
+ user = {name: name, email: email}
9
+ # binding.irb # Commented out debug point
10
+ @users << user
11
+ # debugger # Check user addition
12
+ end
13
+
14
+ def find_user(email)
15
+ debugger
16
+ @users.find { |user| user[:email] == email }
17
+ # binding.break if result.nil?
18
+ end
19
+
20
+ def process_users
21
+ require "debug"
22
+ binding.break
23
+ @users.each do |user|
24
+ puts "Processing #{user[:name]}"
25
+ byebug # Debug each user processing
26
+ end
27
+ end
28
+ end
data/exe/gubed ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "optparse"
5
+ require "gubed"
6
+
7
+ class GubedCLI
8
+ def initialize
9
+ @options = {}
10
+ @parser = create_option_parser
11
+ end
12
+
13
+ def run(args = ARGV)
14
+ @parser.parse!(args)
15
+
16
+ root_path = args.first || "."
17
+
18
+ unless File.directory?(root_path)
19
+ puts "Error: '#{root_path}' is not a valid directory"
20
+ exit 1
21
+ end
22
+
23
+ manager = Gubed::BreakpointManager.new(root_path)
24
+ manager.run
25
+ rescue Interrupt
26
+ puts "\n\nExiting..."
27
+ exit 0
28
+ rescue => e
29
+ puts "Error: #{e.message}"
30
+ exit 1
31
+ end
32
+
33
+ private
34
+
35
+ def create_option_parser
36
+ OptionParser.new do |opts|
37
+ opts.banner = "Usage: gubed [options] [directory]"
38
+ opts.separator ""
39
+ opts.separator "Gubed is a Ruby breakpoint manager that helps you find, manage, and remove"
40
+ opts.separator "debugging breakpoints (binding.pry, debugger, etc.) in your Ruby projects."
41
+ opts.separator ""
42
+ opts.separator "Options:"
43
+
44
+ opts.on("-h", "--help", "Show this help message") do
45
+ puts opts
46
+ exit
47
+ end
48
+
49
+ opts.on("-v", "--version", "Show version") do
50
+ puts "Gubed version #{Gubed::VERSION}"
51
+ exit
52
+ end
53
+
54
+ opts.separator ""
55
+ opts.separator "Examples:"
56
+ opts.separator " gubed # Scan current directory"
57
+ opts.separator " gubed /path/to/project # Scan specific directory"
58
+ opts.separator ""
59
+ opts.separator "Supported breakpoint types:"
60
+ opts.separator " - binding.pry"
61
+ opts.separator " - binding.irb"
62
+ opts.separator " - binding.break"
63
+ opts.separator " - debugger"
64
+ opts.separator " - byebug"
65
+ end
66
+ end
67
+ end
68
+
69
+ GubedCLI.new.run if $0 == __FILE__ || File.basename($0) == "gubed"
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "io/console"
4
+
5
+ module Gubed
6
+ class BreakpointManager
7
+ def initialize(root_path = ".")
8
+ @scanner = BreakpointScanner.new(root_path)
9
+ @breakpoints = []
10
+ @current_index = 0
11
+ @message = nil
12
+ end
13
+
14
+ def run
15
+ refresh_breakpoints
16
+ return puts "No breakpoints found." if @breakpoints.empty?
17
+
18
+ loop do
19
+ show_list
20
+ puts @message if @message
21
+ input = get_input
22
+
23
+ case input
24
+ when "q"
25
+ break if exit_program?(input)
26
+ when "r"
27
+ refresh_breakpoints
28
+ @current_index = 0
29
+ when "t"
30
+ toggle_comment
31
+ when "d"
32
+ delete_breakpoint
33
+ when "v"
34
+ show_context
35
+ when "g"
36
+ prompt_for_breakpoint
37
+ when "j"
38
+ move_down
39
+ when "k"
40
+ move_up
41
+ when "h", "?"
42
+ show_help
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def refresh_breakpoints
50
+ print "Scanning..."
51
+ @breakpoints = @scanner.scan
52
+ puts " found #{@breakpoints.length} breakpoints"
53
+ @current_index = [@current_index, @breakpoints.length - 1].min if @breakpoints.any?
54
+ end
55
+
56
+ def show_list
57
+ system("clear") || system("cls")
58
+ puts "Gubed - Ruby Breakpoint Manager"
59
+ puts "=" * 40
60
+ puts
61
+
62
+ if @breakpoints.empty?
63
+ puts "No breakpoints found."
64
+ return
65
+ end
66
+
67
+ width = @breakpoints.length.to_s.length
68
+ @breakpoints.each_with_index do |bp, index|
69
+ marker = (index == @current_index) ? ">" : " "
70
+ status = bp.commented? ? "[#]" : "[ ]"
71
+ number = (index + 1).to_s.rjust(width)
72
+
73
+ puts "#{marker} #{number}. #{status} #{bp.type} #{bp.location}"
74
+ end
75
+
76
+ puts
77
+ puts "Commands: [j]down [k]up [g]oto [v]iew [t]oggle [d]elete [r]efresh [q]uit [h]elp"
78
+ puts "Selected: #{@current_index + 1} of #{@breakpoints.length}"
79
+ end
80
+
81
+ def get_input
82
+ $stdin.getch
83
+ end
84
+
85
+ def prompt_for_breakpoint
86
+ print "Go to breakpoint: "
87
+ input = gets.chomp
88
+ return if input.empty?
89
+
90
+ num = input.to_i
91
+ if num.between?(1, @breakpoints.length)
92
+ @current_index = num - 1
93
+ else
94
+ @message = "Invalid selection: #{num}."
95
+ end
96
+ end
97
+
98
+ def move_down
99
+ @current_index = (@current_index + 1) % @breakpoints.length
100
+ end
101
+
102
+ def move_up
103
+ @current_index = (@current_index - 1) % @breakpoints.length
104
+ end
105
+
106
+ def show_context
107
+ return if @breakpoints.empty?
108
+
109
+ bp = @breakpoints[@current_index]
110
+ lines = File.readlines(bp.file)
111
+
112
+ puts
113
+ puts "Context for #{bp.location}:"
114
+ puts "-" * 40
115
+
116
+ start_line = [bp.line_number - 6, 1].max
117
+ end_line = [bp.line_number + 4, lines.length].min
118
+
119
+ (start_line..end_line).each do |line_num|
120
+ line_index = line_num - 1
121
+ content = lines[line_index].rstrip
122
+ marker = (line_num == bp.line_number) ? ">>> " : " "
123
+ puts "#{marker}#{line_num}: #{content}"
124
+ end
125
+
126
+ puts
127
+ print "Press Enter to continue..."
128
+ gets
129
+ end
130
+
131
+ def toggle_comment
132
+ return if @breakpoints.empty?
133
+
134
+ bp = @breakpoints[@current_index]
135
+
136
+ modify_line(bp) do |line|
137
+ if bp.commented?
138
+ line.sub(/^(\s*)#\s*/, '\1')
139
+ else
140
+ line.sub(/^(\s*)/, '\1# ')
141
+ end
142
+ end
143
+
144
+ refresh_breakpoints
145
+ end
146
+
147
+ def delete_breakpoint
148
+ return if @breakpoints.empty?
149
+
150
+ bp = @breakpoints[@current_index]
151
+
152
+ print "Delete breakpoint at #{bp.location}? (y/N): "
153
+ response = gets.chomp.downcase
154
+
155
+ return unless response == "y" || response == "yes"
156
+
157
+ lines = File.readlines(bp.file)
158
+ lines.delete_at(bp.line_number - 1)
159
+ File.write(bp.file, lines.join)
160
+
161
+ refresh_breakpoints
162
+ @current_index = [@current_index, @breakpoints.length - 1].min if @breakpoints.any?
163
+ end
164
+
165
+ def exit_program?(input)
166
+ print "Press #{(input == "\e") ? "esc" : input} again to exit or any other key to continue: "
167
+ response = $stdin.getch
168
+ true if response == input
169
+ end
170
+
171
+ def modify_line(breakpoint)
172
+ lines = File.readlines(breakpoint.file)
173
+ lines[breakpoint.line_number - 1] = yield(lines[breakpoint.line_number - 1])
174
+ File.write(breakpoint.file, lines.join)
175
+ end
176
+
177
+ def show_help
178
+ puts
179
+ puts "Commands:"
180
+ puts " j - Move selection down"
181
+ puts " k - Move selection up"
182
+ puts " g - Go to a specific breakpoint by number"
183
+ puts " v - Show context around breakpoint"
184
+ puts " t - Toggle line comment on selected breakpoint"
185
+ puts " d - Delete selected breakpoint"
186
+ puts " r - Rescan for breakpoints"
187
+ puts " q - Exit program"
188
+ puts " h, ? - Show this help"
189
+ puts
190
+ puts "Legend: [ ] = Active, [#] = Commented"
191
+ puts
192
+ print "Press Enter to continue..."
193
+ gets
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "find"
4
+
5
+ module Gubed
6
+ Breakpoint = Data.define(:file, :line_number, :content, :type) do
7
+ def initialize(file:, line_number:, content:, type:)
8
+ super(file: file, line_number: line_number, content: content.strip, type: type)
9
+ end
10
+
11
+ def commented?
12
+ content.start_with?("#")
13
+ end
14
+
15
+ def location
16
+ "#{file}:#{line_number}"
17
+ end
18
+ end
19
+
20
+ class BreakpointScanner
21
+ BREAKPOINT_PATTERNS = {
22
+ "binding.pry" => /^\s*(?:#\s*)?binding\.pry\b/,
23
+ "binding.irb" => /^\s*(?:#\s*)?binding\.irb\b/,
24
+ "binding.break" => /^\s*(?:#\s*)?binding\.break\b/,
25
+ "debugger" => /^\s*(?:#\s*)?debugger\b/,
26
+ "byebug" => /^\s*(?:#\s*)?byebug\b/,
27
+ "debug" => /^\s*(?:#\s*)?(?:require\s+['"]debug['"];\s*)?binding\.break\b/
28
+ }.freeze
29
+
30
+ RUBY_FILE_EXTENSIONS = %w[.rb .rake .gemspec].freeze
31
+ DIR_TO_SKIP = %w[.git vendor node_modules tmp].freeze
32
+
33
+ def initialize(root_path = ".")
34
+ @root_path = File.expand_path(root_path)
35
+ end
36
+
37
+ def scan
38
+ breakpoints = []
39
+
40
+ Find.find(@root_path) do |path|
41
+ if File.directory?(path)
42
+ Find.prune if DIR_TO_SKIP.any? { |dir| path.include?(dir) }
43
+ next
44
+ end
45
+
46
+ next unless ruby_file?(path)
47
+
48
+ breakpoints.concat(scan_file(path))
49
+ end
50
+
51
+ breakpoints.sort_by(&:location)
52
+ end
53
+
54
+ private
55
+
56
+ def ruby_file?(path)
57
+ RUBY_FILE_EXTENSIONS.any? { |ext| path.end_with?(ext) } ||
58
+ (File.executable?(path) && ruby_shebang?(path))
59
+ end
60
+
61
+ def ruby_shebang?(path)
62
+ first_line = begin
63
+ File.open(path, &:readline).strip
64
+ rescue
65
+ ""
66
+ end
67
+ first_line.include?("ruby")
68
+ end
69
+
70
+ def scan_file(file_path)
71
+ breakpoints = []
72
+
73
+ File.foreach(file_path).each_with_index do |line, index|
74
+ BREAKPOINT_PATTERNS.each do |type, pattern|
75
+ if line.match(pattern)
76
+ breakpoints << Breakpoint.new(
77
+ file: file_path,
78
+ line_number: index + 1,
79
+ content: line.chomp,
80
+ type: type
81
+ )
82
+ break
83
+ end
84
+ end
85
+ end
86
+
87
+ breakpoints
88
+ rescue => e
89
+ puts "Warning: Could not scan #{file_path}: #{e.message}"
90
+ []
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gubed
4
+ VERSION = "0.1.0"
5
+ end
data/lib/gubed.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gubed/version"
4
+ require_relative "gubed/breakpoint_scanner"
5
+ require_relative "gubed/breakpoint_manager"
6
+
7
+ module Gubed
8
+ class Error < StandardError; end
9
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gubed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Countz
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Gubed is a standalone command-line tool for finding and managing debugger
13
+ breakpoints (binding.pry, debugger, etc.) in Ruby codebases. Install globally with
14
+ 'gem install gubed' and use in any project.
15
+ email:
16
+ - thomascountz@gmail.com
17
+ executables:
18
+ - gubed
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - CHANGELOG.md
23
+ - LICENSE
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - examples/calculator.rb
28
+ - examples/sample_app.rb
29
+ - exe/gubed
30
+ - lib/gubed.rb
31
+ - lib/gubed/breakpoint_manager.rb
32
+ - lib/gubed/breakpoint_scanner.rb
33
+ - lib/gubed/version.rb
34
+ homepage: https://github.com/thomascountz/gubed
35
+ licenses:
36
+ - MIT
37
+ metadata:
38
+ allowed_push_host: https://rubygems.org
39
+ homepage_uri: https://github.com/thomascountz/gubed
40
+ source_code_uri: https://github.com/thomascountz/gubed
41
+ changelog_uri: https://github.com/thomascountz/gubed/blob/main/CHANGELOG.md
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 3.2.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.6.9
57
+ specification_version: 4
58
+ summary: Ruby CLI for managing and editing debugger breakpoints
59
+ test_files: []