Class: DEBUGGER__::Config

Inherits:
Object show all
Defined in:
lib/debug/config.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv) ⇒ Config

Returns a new instance of Config.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/debug/config.rb', line 69

def initialize argv
  if self.class.config
    raise 'Can not make multiple configurations in one process'
  end

  config = self.class.parse_argv(argv)

  # apply defaults
  CONFIG_SET.each do |k, config_detail|
    unless config.key?(k)
      default_value = config_detail[3]
      config[k] = parse_config_value(k, default_value)
    end
  end

  update config
end

Class Method Details

.configObject



65
66
67
# File 'lib/debug/config.rb', line 65

def self.config
  @config
end

.config_to_env_hash(config) ⇒ Object



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/debug/config.rb', line 442

def self.config_to_env_hash config
  CONFIG_MAP.each_with_object({}){|(key, evname), env|
    unless config[key].nil?
      case CONFIG_SET[key][2]
      when :path
        valstr = config[key].map{|e| e.kind_of?(Regexp) ? e.inspect : e}.join(':')
      when :path_map
        valstr = config[key].map{|e| e.join(':')}.join(',')
      else
        valstr = config[key].to_s
      end
      env[evname] = valstr
    end
  }
end

.parse_argv(argv) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/debug/config.rb', line 267

def self.parse_argv argv
  config = {
    mode: :start,
    no_color: (nc = ENV['NO_COLOR']) && !nc.empty?,
  }
  CONFIG_MAP.each{|key, evname|
    if val = ENV[evname]
      config[key] = parse_config_value(key, val)
    end
  }
  return config if !argv || argv.empty?

  if argv.kind_of? String
    require 'shellwords'
    argv = Shellwords.split(argv)
  end

  require 'optparse'
  require_relative 'version'

  have_shown_version = false

  opt = OptionParser.new do |o|
    o.banner = "#{$0} [options] -- [debuggee options]"
    o.separator ''
    o.version = ::DEBUGGER__::VERSION

    o.separator 'Debug console mode:'
    o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
      config[:nonstop] = '1'
    end

    o.on('-e DEBUG_COMMAND', 'Execute debug command at the beginning of the script.') do |cmd|
      config[:commands] ||= ''
      config[:commands] += cmd + ';;'
    end

    o.on('-x FILE', '--init-script=FILE', 'Execute debug command in the FILE.') do |file|
      config[:init_script] = file
    end
    o.on('--no-rc', 'Ignore ~/.rdbgrc') do
      config[:no_rc] = true
    end
    o.on('--no-color', 'Disable colorize') do
      config[:no_color] = true
    end
    o.on('--no-sigint-hook', 'Disable to trap SIGINT') do
      config[:no_sigint_hook] = true
    end

    o.on('-c', '--command', 'Enable command mode.',
                            'The first argument should be a command name in $PATH.',
                            'Example: \'rdbg -c bundle exec rake test\'') do
      config[:command] = true
    end

    o.separator ''

    o.on('-O', '--open=[FRONTEND]', 'Start remote debugging with opening the network port.',
                                    'If TCP/IP options are not given, a UNIX domain socket will be used.',
                                    'If FRONTEND is given, prepare for the FRONTEND.',
                                    'Now rdbg, vscode and chrome is supported.') do |f|

      case f # some format patterns are not documented yet
      when nil
        config[:open] = true
      when /\A\d\z/
        config[:open] = true
        config[:port] = f.to_i
      when /\A(\S+):(\d+)\z/
        config[:open] = true
        config[:host] = $1
        config[:port] = $2.to_i
      when 'tcp'
        config[:open] = true
        config[:port] ||= 0
      when 'vscode', 'chrome', 'cdp'
        config[:open] = f&.downcase
      else
        raise "Unknown option for --open: #{f}"
      end
    end
    o.on('--sock-path=SOCK_PATH', 'UNIX Domain socket path') do |path|
      config[:sock_path] = path
    end
    o.on('--port=PORT', 'Listening TCP/IP port') do |port|
      config[:port] = port
    end
    o.on('--host=HOST', 'Listening TCP/IP host') do |host|
      config[:host] = host
    end
    o.on('--cookie=COOKIE', 'Set a cookie for connection') do |c|
      config[:cookie] = c
    end
    o.on('--session-name=NAME', 'Session name') do |name|
      config[:session_name] = name
    end

    rdbg = 'rdbg'

    o.separator ''
    o.separator '  Debug console mode runs Ruby program with the debug console.'
    o.separator ''
    o.separator "  '#{rdbg} target.rb foo bar'                starts like 'ruby target.rb foo bar'."
    o.separator "  '#{rdbg} -- -r foo -e bar'                 starts like 'ruby -r foo -e bar'."
    o.separator "  '#{rdbg} -c rake test'                     starts like 'rake test'."
    o.separator "  '#{rdbg} -c -- rake test -t'               starts like 'rake test -t'."
    o.separator "  '#{rdbg} -c bundle exec rake test'         starts like 'bundle exec rake test'."
    o.separator "  '#{rdbg} -O target.rb foo bar'             starts and accepts attaching with UNIX domain socket."
    o.separator "  '#{rdbg} -O --port 1234 target.rb foo bar' starts accepts attaching with TCP/IP localhost:1234."
    o.separator "  '#{rdbg} -O --port 1234 -- -r foo -e bar'  starts accepts attaching with TCP/IP localhost:1234."
    o.separator "  '#{rdbg} target.rb -O chrome --port 1234'  starts and accepts connecting from Chrome Devtools with localhost:1234."

    o.separator ''
    o.separator 'Attach mode:'
    o.on('-A', '--attach', 'Attach to debuggee process.') do
      config[:mode] = :attach
    end

    o.separator ''
    o.separator '  Attach mode attaches the remote debug console to the debuggee process.'
    o.separator ''
    o.separator "  '#{rdbg} -A'           tries to connect via UNIX domain socket."
    o.separator "  #{' ' * rdbg.size}                If there are multiple processes are waiting for the"
    o.separator "  #{' ' * rdbg.size}                debugger connection, list possible debuggee names."
    o.separator "  '#{rdbg} -A path'      tries to connect via UNIX domain socket with given path name."
    o.separator "  '#{rdbg} -A port'      tries to connect to localhost:port via TCP/IP."
    o.separator "  '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."

    o.separator ''
    o.separator 'Other options:'

    o.on('-v', 'Show version number') do
      puts o.ver
      have_shown_version = true
    end

    o.on('--version', 'Show version number and exit') do
      puts o.ver
      exit
    end

    o.on("-h", "--help", "Print help") do
      puts o
      exit
    end

    o.on('--util=NAME', 'Utility mode (used by tools)') do |name|
      require_relative 'client'
      Client.util(name)
      exit
    end

    o.on('--stop-at-load', 'Stop immediately when the debugging feature is loaded.') do
      config[:stop_at_load] = true
    end

    o.separator ''
    o.separator 'NOTE'
    o.separator '  All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
    o.separator '  Please use the remote debugging feature carefully.'
  end

  opt.parse!(argv)

  if argv.empty?
    case
    when have_shown_version && config[:mode] == :start
      exit
    end
  end

  config
end

.parse_config_value(name, valstr) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/debug/config.rb', line 226

def self.parse_config_value name, valstr
  return valstr unless valstr.kind_of? String

  case CONFIG_SET[name][2]
  when :bool
    case valstr
    when '1', 'true', 'TRUE', 'T'
      true
    else
      false
    end
  when :int
    valstr.to_i
  when :loglevel
    if DEBUGGER__::LOG_LEVELS[s = valstr.to_sym]
      s
    else
      raise "Unknown loglevel: #{valstr}"
    end
  when :forkmode
    case sym = valstr.to_sym
    when :parent, :child, :both, nil
      sym
    else
      raise "unknown fork mode: #{sym}"
    end
  when :path # array of String
    valstr.split(/:/).map{|e|
      if /\A\/(.+)\/\z/ =~ e
        Regexp.compile $1
      else
        e
      end
    }
  when :path_map
    valstr.split(',').map{|e| e.split(':')}
  else
    valstr
  end
end

Instance Method Details

#[](key) ⇒ Object



91
92
93
# File 'lib/debug/config.rb', line 91

def [](key)
  config[key]
end

#[]=(key, val) ⇒ Object



95
96
97
# File 'lib/debug/config.rb', line 95

def []=(key, val)
  set_config(key => val)
end

#append_config(key, val) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/debug/config.rb', line 112

def append_config key, val
  conf = config.dup

  if CONFIG_SET[key]
    if CONFIG_SET[key][2] == :path
      conf[key] = [*conf[key], *parse_config_value(key, val)];
    else
      raise "not an Array type: #{key}"
    end
  else
    raise "Unknown configuration: #{key}"
  end

  update conf
end

#inspectObject



87
88
89
# File 'lib/debug/config.rb', line 87

def inspect
  config.inspect
end

#set_config(**kw) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/debug/config.rb', line 99

def set_config(**kw)
  conf = config.dup
  kw.each{|k, v|
    if CONFIG_MAP[k]
      conf[k] = parse_config_value(k, v) # TODO: ractor support
    else
      raise "Unknown configuration: #{k}"
    end
  }

  update conf
end

#update(conf) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/debug/config.rb', line 128

def update conf
  old_conf = self.class.instance_variable_get(:@config) || {}

  # TODO: Use Ractor.make_shareable(conf)
  self.class.instance_variable_set(:@config, conf.freeze)

  # Post process
  if_updated old_conf, conf, :keep_alloc_site do |old, new|
    if new
      require 'objspace'
      ObjectSpace.trace_object_allocations_start
    end

    if old && !new
      ObjectSpace.trace_object_allocations_stop
    end
  end

  if_updated old_conf, conf, :postmortem do |_, new_p|
    if defined?(SESSION)
      SESSION.postmortem = new_p
    end
  end

  if_updated old_conf, conf, :sigdump_sig do |old_sig, new_sig|
    setup_sigdump old_sig, new_sig
  end

  if_updated old_conf, conf, :no_sigint_hook do |old, new|
    if defined?(SESSION)
      SESSION.set_no_sigint_hook old, new
    end
  end

  if_updated old_conf, conf, :irb_console do |old, new|
    if defined?(SESSION) && SESSION.active?
      # irb_console is switched from true to false
      if old
        SESSION.deactivate_irb_integration
      # irb_console is switched from false to true
      else
        if CONFIG[:open]
          SESSION.instance_variable_get(:@ui).puts "\nIRB is not supported on the remote console."
        else
          SESSION.activate_irb_integration
        end
      end
    end
  end
end