module DEBUGGER__

Experimental RBS support (using type sampling data from the type_fusion project).

# sig/debug/abbrev_command.rbs

module DEBUGGER__
  def self.check_loglevel: (Symbol level) -> false
  def self.debug: () -> nil
  def self.info: (String msg) -> nil
  def self.log: (Symbol level, String msg) -> nil
end

def self.add_catch_breakpoint pat

def self.add_catch_breakpoint pat
  ::DEBUGGER__::SESSION.add_catch_breakpoint pat
end

def self.add_line_breakpoint file, line, **kw

def self.add_line_breakpoint file, line, **kw
  ::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
end

def self.check_dir_authority path

def self.check_dir_authority path
  fs = File.stat(path)
  unless (dir_uid = fs.uid) == (uid = Process.uid)
    raise "#{path} uid is #{dir_uid}, but Process.uid is #{uid}"
  end
  if fs.world_writable? && !fs.sticky?
    raise "#{path} is world writable but not sticky"
  end
  path
end

def self.check_loglevel level

Experimental RBS support (using type sampling data from the type_fusion project).

def self.check_loglevel: (Symbol level) -> false

This signature was generated using 1101 samples from 5 applications.

def self.check_loglevel level
  lv = LOG_LEVELS[level]
  config_lv = LOG_LEVELS[CONFIG[:log_level]]
  lv <= config_lv
end

def self.commands

def self.commands
  (defined?(@commands) && @commands) || (parse_help; @commands)
end

def self.compare_path(a, b)

def self.compare_path(a, b)
  a&.downcase == b&.downcase
end

def self.compare_path(a, b)

def self.compare_path(a, b)
  a == b
end

def self.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir)

def self.create_unix_domain_socket_name(base_dir = unix_domain_socket_dir)
  create_unix_domain_socket_name_prefix(base_dir) + "-#{Process.pid}"
end

def self.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)

def self.create_unix_domain_socket_name_prefix(base_dir = unix_domain_socket_dir)
  user = ENV['USER'] || 'UnknownUser'
  File.join(base_dir, "ruby-debug-#{user}")
end

def self.debug(&b)

Experimental RBS support (using type sampling data from the type_fusion project).

def self.debug: () -> nil

This signature was generated using 548 samples from 4 applications.

def self.debug(&b)
  if check_loglevel :DEBUG
    log :DEBUG, b.call
  end
end

def self.help

def self.help
  r = []
  self.helps.each{|cat, cmds|
    r << "### #{cat}"
    r << ''
    cmds.each{|_, desc|
      r << desc
    }
    r << ''
  }
  r.join("\n")
end

def self.helps

def self.helps
  (defined?(@helps) && @helps) || parse_help
end

def self.info msg

Experimental RBS support (using type sampling data from the type_fusion project).

def self.info: (String msg) -> nil

This signature was generated using 544 samples from 5 applications.

def self.info msg
  log :INFO, msg
end

def self.load_rc

def self.load_rc
  [[File.expand_path('~/.rdbgrc'), true],
   [File.expand_path('~/.rdbgrc.rb'), true],
   # ['./.rdbgrc', true], # disable because of security concern
   [CONFIG[:init_script], false],
   ].each{|(path, rc)|
    next unless path
    next if rc && CONFIG[:no_rc] # ignore rc
    if File.file? path
      if path.end_with?('.rb')
        load path
      else
        ::DEBUGGER__::SESSION.add_preset_commands path, File.readlines(path)
      end
    elsif !rc
      warn "Not found: #{path}"
    end
  }
  # given debug commands
  if CONFIG[:commands]
    cmds = CONFIG[:commands].split(';;')
    ::DEBUGGER__::SESSION.add_preset_commands "commands", cmds, kick: false, continue: false
  end
end

def self.log level, msg

Experimental RBS support (using type sampling data from the type_fusion project).

def self.log: (Symbol level, String msg) -> nil

This signature was generated using 603 samples from 5 applications.

def self.log level, msg
  if check_loglevel level
    @logfile = STDERR unless defined? @logfile
    return if @logfile.closed?
    if defined? SESSION
      pi = SESSION.process_info
      process_info = pi ? "[#{pi}]" : nil
    end
    if level == :WARN
      # :WARN on debugger is general information
      @logfile.puts "DEBUGGER#{process_info}: #{msg}"
      @logfile.flush
    else
      @logfile.puts "DEBUGGER#{process_info} (#{level}): #{msg}"
      @logfile.flush
    end
  end
end

def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw

def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
  CONFIG.set_config(**kw)
  require_relative 'server'
  if port || CONFIG[:open] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
    open_tcp host: host, port: (port || 0), nonstop: nonstop
  else
    open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
  end
end

def self.open_tcp host: nil, port:, nonstop: false, **kw

def self.open_tcp host: nil, port:, nonstop: false, **kw
  CONFIG.set_config(**kw)
  require_relative 'server'
  if defined? SESSION
    SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
  else
    initialize_session{ UI_TcpServer.new(host: host, port: port) }
  end
  setup_initial_suspend unless nonstop
end

def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw

def self.open_unix sock_path: nil, sock_dir: nil, nonstop: false, **kw
  CONFIG.set_config(**kw)
  require_relative 'server'
  if defined? SESSION
    SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
  else
    initialize_session{ UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path) }
  end
  setup_initial_suspend unless nonstop
end

def self.parse_help

def self.parse_help
  helps = Hash.new{|h, k| h[k] = []}
  desc = cat = nil
  cmds = Hash.new
  File.read(File.join(__dir__, 'session.rb'), encoding: Encoding::UTF_8).each_line do |line|
    case line
    when /\A\s*### (.+)/
      cat = $1
      break if $1 == 'END'
    when /\A      register_command (.+)/
      next unless cat
      next unless desc
      ws = []
      $1.gsub(/'([a-z]+)'/){|w|
        ws << $1
      }
      helps[cat] << [ws, desc]
      desc = nil
      max_w = ws.max_by{|w| w.length}
      ws.each{|w|
        cmds[w] = max_w
      }
    when /\A\s+# (\s*\*.+)/
      if desc
        desc << "\n" + $1
      else
        desc = $1
      end
    end
  end
  @commands = cmds
  @helps = helps
end

def self.require_location

nil for -r
String for requiring location
def self.require_location
  locs = caller_locations
  dir_prefix = /#{Regexp.escape(__dir__)}/
  locs.each do |loc|
    case loc.absolute_path
    when dir_prefix
    when %r{rubygems/core_ext/kernel_require\.rb}
    else
      return loc if loc.absolute_path
    end
  end
  nil
end

def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false

def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
  if short
    LimitedPP.pp(obj, max_length)
  else
    obj.inspect
  end
rescue NoMethodError => e
  klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
  if obj == (r = e.receiver)
    "<\##{klass.name}#{oid} does not have \#inspect>"
  else
    rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
    "<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
  end
rescue Exception => e
  "<#inspect raises #{e.inspect}>"
end

def self.setup_initial_suspend

def self.setup_initial_suspend
  if !CONFIG[:nonstop]
    case
    when CONFIG[:stop_at_load]
      add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true, hook_call: false
      nil # stop here
    when path = ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH']
      add_line_breakpoint path, 0, oneshot: true, hook_call: false
    when loc = ::DEBUGGER__.require_location
      # require 'debug/start' or 'debug'
      add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
    else
      # -r
      add_line_breakpoint $0, 0, oneshot: true, hook_call: false
    end
  end
end

def self.start nonstop: false, **kw

def self.start nonstop: false, **kw
  CONFIG.set_config(**kw)
  if CONFIG[:open]
    open nonstop: nonstop, **kw
  else
    unless defined? SESSION
      require_relative 'local'
      initialize_session{ UI_LocalConsole.new }
    end
    setup_initial_suspend unless nonstop
  end
end

def self.step_in &b

def self.step_in &b
  if defined?(SESSION) && SESSION.active?
    SESSION.add_iseq_breakpoint RubyVM::InstructionSequence.of(b), oneshot: true
  end
  yield
end

def self.unix_domain_socket_dir

def self.unix_domain_socket_dir
  case
  when path = CONFIG[:sock_dir]
  when path = ENV['XDG_RUNTIME_DIR']
  when path = unix_domain_socket_tmpdir
  when path = unix_domain_socket_homedir
  else
    raise 'specify RUBY_DEBUG_SOCK_DIR environment variable.'
  end
  path
end

def self.unix_domain_socket_homedir

def self.unix_domain_socket_homedir
  if home = ENV['HOME']
    path = File.join(home, '.ruby-debug-sock')
    unless File.exist?(path)
      Dir.mkdir(path, 0700)
    end
    check_dir_authority(path)
  end
end

def self.unix_domain_socket_tmpdir

def self.unix_domain_socket_tmpdir
  require 'tmpdir'
  if tmpdir = Dir.tmpdir
    path = File.join(tmpdir, "ruby-debug-sock-#{Process.uid}")
    unless File.exist?(path)
      d = Dir.mktmpdir
      File.rename(d, path)
    end
    check_dir_authority(path)
  end
end

def self.warn msg

def self.warn msg
  log :WARN, msg
end

def skip?

def skip?
  @skip_all
end

def skip_all

def skip_all
  @skip_all = true
end