debug
debug copied to clipboard
[POC] Command Registration
Benefits
Extensible
Users or gems will be able to register new commands with:
DEBUGGER__::Session.register_command("foo", aliases: ["fo"]) do |arg|
# my command
end.document category: "My Gem", content: <<~MD
* foo does blablabla
MD
And then the command document will appear in the help
❯ ruby -Ilib -rdebug target.rb
[4, 9] in target.rb
4| # my command
5| end.document category: "My Gem", content: <<~MD
6| * foo does blablabla
7| MD
8|
=> 9| binding.b
=>#0 <main> at target.rb:9
(rdbg) help foo # command
### My Gem
* foo does blablabla
(rdbg)
And because commands are stored together, it can support autocompletion in the future too.
This extensibility will allow:
- Integration with popular libraries, like Rails integration demonstrated in the comments below.
- 3rd-party command implementations. We can have commands like show-source implemented in a 3rd-party gem instead of implementing everything in the debugger core. (not saying that
show-sourceshould be implemented externally, but just an example).
Easier to maintain
Currently, command documents need to be written in Ruby comments with a strict format. And the parsing logic is quite adhoc for that reason. With this approach, we can simplify the parsing logic and make the document naturally a part of the command definition.
Additional thoughts
- The
CommandSetlogic is a replication of theConfigclass, which prevents sharing mutable hash while allowing modifying values. I hope this will make it Ractor-safe?
Do you want to invoke some Ruby code on the session thread?
Could you list the example usages? This is a kind of language design and examples are very important.
Example - fetching Rails configurations via rails config
Command Code
COMMAND_OPTIONS_SPLITTING_REGEXP = /([^\s]+)(?:\s+(.+))?/
DEBUGGER__::Session.register_command("rails") do |arg|
COMMAND_OPTIONS_SPLITTING_REGEXP =~ arg
sub_command, options = $1, $2
case sub_command
when "config"
config = ::Rails.application.config
configs =
if options
if config.respond_to?(options)
config.send(options)
else
@ui.puts("unknown Rails component #{options}")
[]
end
else
config.instance_variables.each_with_object({}) do |ivar, hash|
key = ivar.to_s.sub("@", "")
value = config.instance_variable_get(ivar)
hash[key] = value
end
end
configs.each do |key, value|
key = colorize_cyan(key)
@ui.puts(" #{key} => #{DEBUGGER__.safe_inspect(value)}")
end
else
@ui.puts("unknown Rails debuggger command: #{arg}")
end
:retry
end.document category: "Rails", content: <<~MD
* `rails config`
* List top-level Rails configurations
* `rails config <component>`
* List <component>'s configurations
MD
Result
Other useful command extensions for Rails could be:
rails breadcrumbs- See all the recordedActiveSupport::Notificationsevents.- Of course this needs extra work to store/access the events but it's relatively easy to achieve without debugger's support.
- The breadcrumbs concept exists in many error monitoring services like Sentry. It'll help users track major application events happened before the program reaches the current state
rails callbacks Model- List a model's callbacks by runningModel._save_callbacksand filter/display the result nicelyrails routes /regexp/- List the application's routes
@rafaelfranca is there other Rails specific debugger commands you'd like to see as well?
So they are global information.
# define SOME_CLASS.some_method before
eval SOME_CLASS.some_method
doesn't work?
I didn't mean "use eval" on this but providing alias feature like:
alias rails "eval SOME_CLASS.some_method(args)"
with documentation.
Ah, @ui.puts is not work well. adding eval option?
Anyway, I don't think it is good idea to expose @ui.puts for extensions.
alias rails "eval SOME_CLASS.some_method(args)"
That could work too. But I think alias is confusing and something like command should be clearer, along with an argument for documentation
command :rails, "eval RailsDebugger.execute(args)", doc: <<~DOC
* `rails config`
* List top-level Rails configurations
* `rails config <component>`
* List <component>'s configurations
DOC
Anyway, I don't think it is good idea to expose @ui.puts for extensions.
Yeah it's not ideal to expose instance variables. But we still need to access the UI from the extension code. Do you think calling DEBUGGER__::SESSION.print(msg) will be acceptable?