123456789_123456789_123456789_123456789_123456789_

Class: DEBUGGER__::Client

Relationships & Source Files
Inherits: Object
Defined in: lib/debug/client.rb

Class Method Summary

Instance Method Summary

Constructor Details

.new(argv) ⇒ Client

[ GitHub ]

  
# File 'lib/debug/client.rb', line 118

def initialize argv
  @multi_process = false
  @pid = nil
  @console = Console.new

  case argv.size
  when 0
    connect_unix
  when 1
    if /\A\d+\z/ =~ (arg = argv.shift.strip)
      connect_tcp nil, arg.to_i
    else
      connect_unix arg
    end
  when 2
    connect_tcp argv[0], argv[1]
  else
    raise CommandLineOptionError
  end

  @width = IO.console_size[1]
  @width = 80 if @width == 0

  send "version: #{VERSION} " +
       "width: #{@width} " +
       "cookie: #{CONFIG[:cookie] || '-'} " +
       "nonstop: #{CONFIG[:nonstop] ? 'true' : 'false'}"
end

Class Method Details

.cleanup_unix_domain_sockets

[ GitHub ]

  
# File 'lib/debug/client.rb', line 84

def cleanup_unix_domain_sockets
  Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*') do |file|
    if File.socket?(file) && (/-(\d)-\d$/ =~ file || /-(\d+)$/ =~ file)
      begin
        Process.kill(0, $1.to_i)
      rescue Errno::EPERM
      rescue Errno::ESRCH
        File.unlink(file)
      end
    end
  end
end

.list_connections(verbose: false)

[ GitHub ]

  
# File 'lib/debug/client.rb', line 97

def list_connections verbose: false
  socks = Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*').find_all do |path|
    File.socket?(path)
  end

  if verbose
    socks = socks.map{|sock_path|
      Socket.unix(sock_path){|sock|
        sock.puts "info cookie: #{CONFIG[:cookie] || '-'}"
        pid = sock.gets.chomp
        _dbg = sock.gets.chomp
        _unm = sock.gets.chomp
        [sock_path, pid]
      }
    }
  end

  socks
end

.setup_autoload

[ GitHub ]

  
# File 'lib/debug/client.rb', line 56

def setup_autoload
  prelude_path = File.join(__dir__, 'prelude.rb')

  case shell = working_shell_type
  when :bash, :zsh
    puts <<~EOS
    # add the following lines in your ~/.#{shell}_profile

    if test -s #{prelude_path} ; then
      export RUBYOPT='-r #{prelude_path}'
    fi

    # Add `Kernel#bb` method which is alias of `Kernel#debugger`
    # export RUBY_DEBUG_BB=1
    EOS

  when :fish
    puts <<~EOS
    # add the following lines in your ~/.config/fish/config.fish
    set -x RUBYOPT "-r #{__dir__}/prelude" $RUBYOPT
    EOS

  else
    puts "# Sorry that your shell is not supported yet.",
         "# Please use the content in #{prelude_path} as a reference and modify your login script accordingly."
  end
end

.util(name)

[ GitHub ]

  
# File 'lib/debug/client.rb', line 17

def util name
  case name
  when 'gen-sockpath'
    puts DEBUGGER__.create_unix_domain_socket_name
  when 'gen-portpath'
    port_path = File.join(DEBUGGER__.unix_domain_socket_dir, 'tcp_port')
    File.unlink port_path if File.exist?(port_path)
    puts port_path
  when 'list-socks'
    cleanup_unix_domain_sockets
    puts list_connections
  when 'list-socks-verbose'
    cleanup_unix_domain_sockets
    puts list_connections verbose: true
  when 'setup-autoload'
    setup_autoload
  else
    abort "Unknown utility: #{name}"
  end
end

.working_shell_type

[ GitHub ]

  
# File 'lib/debug/client.rb', line 38

def working_shell_type
  shell = `ps -p #{Process.ppid} -o 'args='`
  case shell
  when /bash/
    :bash
  when /fish/
    :fish
  when /csh/
    :csh
  when /zsh/
    :zsh
  when /dash/
    :dash
  else
    :unknown
  end
end

Instance Method Details

#connect

[ GitHub ]

  
# File 'lib/debug/client.rb', line 196

def connect
  pre_commands = (CONFIG[:commands] || '').split(';;')

  trap(:SIGINT){
    send "pause"
  }

  begin
    trap(:SIGWINCH){
      @width = IO.console_size[1]
    }
  rescue ArgumentError
    @width = 80
  end

  while line = @s.gets
    p recv: line if $VERBOSE
    case line

    when /^out (.*)/
      puts "#{$1}"

    when /^input (.+)/
      pid = $1
      @multi_process = true if @pid && @pid != pid
      @pid = pid
      prev_trap = trap(:SIGINT, 'DEFAULT')

      begin
        if pre_commands.empty?
          line = readline
        else
          line = pre_commands.shift
          puts "(rdbg:remote:command) #{line}"
        end
      rescue Interrupt
        retry
      ensure
        trap(:SIGINT, prev_trap)
      end

      line = (line || 'quit').strip
      send "command #{pid} #{@width} #{line}"

    when /^ask (\d+) (.*)/
      pid = $1
      print $2
      send "answer #{pid} #{$stdin.gets || ''}"

    when /^quit/
      raise 'quit'

    else
      puts "(unknown) #{line.inspect}"
    end
  end
rescue => e
  STDERR.puts "disconnected (#{e})"
  exit
ensure
  deactivate
end

#connect_tcp(host, port)

[ GitHub ]

  
# File 'lib/debug/client.rb', line 187

def connect_tcp host, port
  @s = Socket.tcp(host, port)
end

#connect_unix(name = nil)

[ GitHub ]

  
# File 'lib/debug/client.rb', line 159

def connect_unix name = nil
  if name
    if File.exist? name
      @s = Socket.unix(name)
    else
      @s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_dir, name))
    end
  else
    Client.cleanup_unix_domain_sockets
    files = Client.list_connections

    case files.size
    when 0
      $stderr.puts "No debug session is available."
      exit
    when 1
      @s = Socket.unix(files.first)
    else
      files = Client.list_connections verbose: true
      $stderr.puts "Please select a debug session:"
      files.each{|(f, desc)|
        $stderr.puts "  #{File.basename(f)} (#{desc})"
      }
      exit
    end
  end
end

#deactivate

[ GitHub ]

  
# File 'lib/debug/client.rb', line 147

def deactivate
  @console.deactivate if @console
end

#readline

[ GitHub ]

  
# File 'lib/debug/client.rb', line 151

def readline
  if @multi_process
    @console.readline "(rdbg:remote\##{@pid}) "
  else
    @console.readline "(rdbg:remote) "
  end
end

#send(msg)

[ GitHub ]

  
# File 'lib/debug/client.rb', line 191

def send msg
  p send: msg if $VERBOSE
  @s.puts msg
end