debug 1.7.1 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/debug/session.rb CHANGED
@@ -82,7 +82,7 @@ class RubyVM::InstructionSequence
82
82
  def first_line
83
83
  self.to_a[4][:code_location][0]
84
84
  end unless method_defined?(:first_line)
85
- end
85
+ end if defined?(RubyVM::InstructionSequence)
86
86
 
87
87
  module DEBUGGER__
88
88
  PresetCommands = Struct.new(:commands, :source, :auto_continue)
@@ -128,16 +128,16 @@ module DEBUGGER__
128
128
  @obj_map = {} # { object_id => ... } for CDP
129
129
 
130
130
  @tp_thread_begin = nil
131
+ @tp_thread_end = nil
132
+
131
133
  @commands = {}
132
134
  @unsafe_context = false
133
135
 
134
- has_keep_script_lines = RubyVM.respond_to? :keep_script_lines
136
+ @has_keep_script_lines = defined?(RubyVM.keep_script_lines)
135
137
 
136
138
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
137
- if !has_keep_script_lines || bps_pending_until_load?
138
- eval_script = tp.eval_script unless has_keep_script_lines
139
- ThreadClient.current.on_load tp.instruction_sequence, eval_script
140
- end
139
+ eval_script = tp.eval_script unless @has_keep_script_lines
140
+ ThreadClient.current.on_load tp.instruction_sequence, eval_script
141
141
  }
142
142
  @tp_load_script.enable
143
143
 
@@ -169,12 +169,17 @@ module DEBUGGER__
169
169
  @ui = ui if ui
170
170
 
171
171
  @tp_thread_begin&.disable
172
+ @tp_thread_end&.disable
172
173
  @tp_thread_begin = nil
173
-
174
+ @tp_thread_end = nil
174
175
  @ui.activate self, on_fork: on_fork
175
176
 
176
177
  q = Queue.new
178
+ first_q = Queue.new
177
179
  @session_server = Thread.new do
180
+ # make sure `@session_server` is assigned
181
+ first_q.pop; first_q = nil
182
+
178
183
  Thread.current.name = 'DEBUGGER__::SESSION@server'
179
184
  Thread.current.abort_on_exception = true
180
185
 
@@ -192,10 +197,21 @@ module DEBUGGER__
192
197
  end
193
198
  @tp_thread_begin.enable
194
199
 
200
+ @tp_thread_end = TracePoint.new(:thread_end) do |tp|
201
+ @th_clients.delete(Thread.current)
202
+ end
203
+ @tp_thread_end.enable
204
+
205
+ if CONFIG[:irb_console] && !CONFIG[:open]
206
+ require_relative "irb_integration"
207
+ thc.activate_irb_integration
208
+ end
209
+
195
210
  # session start
196
211
  q << true
197
212
  session_server_main
198
213
  end
214
+ first_q << :ok
199
215
 
200
216
  q.pop
201
217
  end
@@ -205,6 +221,7 @@ module DEBUGGER__
205
221
  @thread_stopper.disable
206
222
  @tp_load_script.disable
207
223
  @tp_thread_begin.disable
224
+ @tp_thread_end.disable
208
225
  @bps.each_value{|bp| bp.disable}
209
226
  @th_clients.each_value{|thc| thc.close}
210
227
  @tracers.values.each{|t| t.disable}
@@ -219,11 +236,13 @@ module DEBUGGER__
219
236
 
220
237
  # activate new ui
221
238
  @tp_thread_begin.disable
239
+ @tp_thread_end.disable
222
240
  @ui.activate self
223
241
  if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
224
242
  thc.mark_as_management
225
243
  end
226
244
  @tp_thread_begin.enable
245
+ @tp_thread_end.enable
227
246
  end
228
247
 
229
248
  def pop_event
@@ -242,6 +261,15 @@ module DEBUGGER__
242
261
  @tc << req
243
262
  end
244
263
 
264
+ def request_tc_with_restarted_threads(req)
265
+ restart_all_threads
266
+ request_tc(req)
267
+ end
268
+
269
+ def request_eval type, src
270
+ request_tc_with_restarted_threads [:eval, type, src]
271
+ end
272
+
245
273
  def process_event evt
246
274
  # variable `@internal_info` is only used for test
247
275
  tc, output, ev, @internal_info, *ev_args = evt
@@ -300,7 +328,7 @@ module DEBUGGER__
300
328
  if @displays.empty?
301
329
  wait_command_loop
302
330
  else
303
- request_tc [:eval, :display, @displays]
331
+ request_eval :display, @displays
304
332
  end
305
333
  when :result
306
334
  raise "[BUG] not in subsession" if @subsession_stack.empty?
@@ -315,6 +343,7 @@ module DEBUGGER__
315
343
  end
316
344
  end
317
345
 
346
+ stop_all_threads
318
347
  when :method_breakpoint, :watch_breakpoint
319
348
  bp = ev_args[1]
320
349
  if bp
@@ -328,17 +357,15 @@ module DEBUGGER__
328
357
  obj_inspect = ev_args[2]
329
358
  opt = ev_args[3]
330
359
  add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
360
+ stop_all_threads
331
361
  else
332
- # ignore
362
+ stop_all_threads
333
363
  end
334
364
 
335
365
  wait_command_loop
336
366
 
337
- when :dap_result
338
- dap_event ev_args # server.rb
339
- wait_command_loop
340
- when :cdp_result
341
- cdp_event ev_args
367
+ when :protocol_result
368
+ process_protocol_result ev_args
342
369
  wait_command_loop
343
370
  end
344
371
  end
@@ -480,9 +507,9 @@ module DEBUGGER__
480
507
  # * `u[ntil]`
481
508
  # * Similar to `next` command, but only stop later lines or the end of the current frame.
482
509
  # * Similar to gdb's `advance` command.
483
- # * `u[ntil] <[file:]line>
510
+ # * `u[ntil] <[file:]line>`
484
511
  # * Run til the program reaches given location or the end of the current frame.
485
- # * `u[ntil] <name>
512
+ # * `u[ntil] <name>`
486
513
  # * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
487
514
  register_command 'u', 'until',
488
515
  repeat: true,
@@ -674,15 +701,15 @@ module DEBUGGER__
674
701
  register_command 'bt', 'backtrace', unsafe: false do |arg|
675
702
  case arg
676
703
  when /\A(\d+)\z/
677
- request_tc [:show, :backtrace, arg.to_i, nil]
704
+ request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
678
705
  when /\A\/(.*)\/\z/
679
706
  pattern = $1
680
- request_tc [:show, :backtrace, nil, Regexp.compile(pattern)]
707
+ request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
681
708
  when /\A(\d+)\s+\/(.*)\/\z/
682
709
  max, pattern = $1, $2
683
- request_tc [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
710
+ request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
684
711
  else
685
- request_tc [:show, :backtrace, nil, nil]
712
+ request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
686
713
  end
687
714
  end
688
715
 
@@ -799,15 +826,15 @@ module DEBUGGER__
799
826
 
800
827
  case sub
801
828
  when nil
802
- request_tc [:show, :default, pat] # something useful
829
+ request_tc_with_restarted_threads [:show, :default, pat] # something useful
803
830
  when :locals
804
- request_tc [:show, :locals, pat]
831
+ request_tc_with_restarted_threads [:show, :locals, pat]
805
832
  when :ivars
806
- request_tc [:show, :ivars, pat, opt]
833
+ request_tc_with_restarted_threads [:show, :ivars, pat, opt]
807
834
  when :consts
808
- request_tc [:show, :consts, pat, opt]
835
+ request_tc_with_restarted_threads [:show, :consts, pat, opt]
809
836
  when :globals
810
- request_tc [:show, :globals, pat]
837
+ request_tc_with_restarted_threads [:show, :globals, pat]
811
838
  when :threads
812
839
  thread_list
813
840
  :retry
@@ -827,7 +854,7 @@ module DEBUGGER__
827
854
  # * Show you available methods and instance variables of the given object.
828
855
  # * If the object is a class/module, it also lists its constants.
829
856
  register_command 'outline', 'o', 'ls', unsafe: false do |arg|
830
- request_tc [:show, :outline, arg]
857
+ request_tc_with_restarted_threads [:show, :outline, arg]
831
858
  end
832
859
 
833
860
  # * `display`
@@ -837,9 +864,9 @@ module DEBUGGER__
837
864
  register_command 'display', postmortem: false do |arg|
838
865
  if arg && !arg.empty?
839
866
  @displays << arg
840
- request_tc [:eval, :try_display, @displays]
867
+ request_eval :try_display, @displays
841
868
  else
842
- request_tc [:eval, :display, @displays]
869
+ request_eval :display, @displays
843
870
  end
844
871
  end
845
872
 
@@ -853,7 +880,7 @@ module DEBUGGER__
853
880
  if @displays[n = $1.to_i]
854
881
  @displays.delete_at n
855
882
  end
856
- request_tc [:eval, :display, @displays]
883
+ request_eval :display, @displays
857
884
  when nil
858
885
  if ask "clear all?", 'N'
859
886
  @displays.clear
@@ -889,13 +916,13 @@ module DEBUGGER__
889
916
  # * `p <expr>`
890
917
  # * Evaluate like `p <expr>` on the current frame.
891
918
  register_command 'p' do |arg|
892
- request_tc [:eval, :p, arg.to_s]
919
+ request_eval :p, arg.to_s
893
920
  end
894
921
 
895
922
  # * `pp <expr>`
896
923
  # * Evaluate like `pp <expr>` on the current frame.
897
924
  register_command 'pp' do |arg|
898
- request_tc [:eval, :pp, arg.to_s]
925
+ request_eval :pp, arg.to_s
899
926
  end
900
927
 
901
928
  # * `eval <expr>`
@@ -906,7 +933,7 @@ module DEBUGGER__
906
933
  @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
907
934
  :retry
908
935
  else
909
- request_tc [:eval, :call, arg]
936
+ request_eval :call, arg
910
937
  end
911
938
  end
912
939
 
@@ -914,10 +941,11 @@ module DEBUGGER__
914
941
  # * Invoke `irb` on the current frame.
915
942
  register_command 'irb' do |arg|
916
943
  if @ui.remote?
917
- @ui.puts "not supported on the remote console."
944
+ @ui.puts "\nIRB is not supported on the remote console."
918
945
  :retry
946
+ else
947
+ request_eval :irb, nil
919
948
  end
920
- request_tc [:eval, :irb]
921
949
  end
922
950
 
923
951
  ### Trace
@@ -972,7 +1000,7 @@ module DEBUGGER__
972
1000
  :retry
973
1001
 
974
1002
  when /\Aobject\s+(.+)/
975
- request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}]
1003
+ request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]
976
1004
 
977
1005
  when /\Aoff\s+(\d+)\z/
978
1006
  if t = @tracers.values[$1.to_i]
@@ -1137,7 +1165,7 @@ module DEBUGGER__
1137
1165
  @repl_prev_line = nil
1138
1166
  check_unsafe
1139
1167
 
1140
- request_tc [:eval, :pp, line]
1168
+ request_eval :pp, line
1141
1169
  end
1142
1170
 
1143
1171
  rescue Interrupt
@@ -1321,10 +1349,6 @@ module DEBUGGER__
1321
1349
 
1322
1350
  # breakpoint management
1323
1351
 
1324
- def bps_pending_until_load?
1325
- @bps.any?{|key, bp| bp.pending_until_load?}
1326
- end
1327
-
1328
1352
  def iterate_bps
1329
1353
  deleted_bps = []
1330
1354
  i = 0
@@ -1500,7 +1524,7 @@ module DEBUGGER__
1500
1524
  def clear_line_breakpoints path
1501
1525
  path = resolve_path(path)
1502
1526
  clear_breakpoints do |k, bp|
1503
- bp.is_a?(LineBreakpoint) && DEBUGGER__.compare_path(k.first, path)
1527
+ bp.is_a?(LineBreakpoint) && bp.path_is?(path)
1504
1528
  end
1505
1529
  rescue Errno::ENOENT
1506
1530
  # just ignore
@@ -1524,7 +1548,7 @@ module DEBUGGER__
1524
1548
  # tracers
1525
1549
 
1526
1550
  def add_tracer tracer
1527
- if @tracers.has_key? tracer.key
1551
+ if @tracers[tracer.key]&.enabled?
1528
1552
  tracer.disable
1529
1553
  @ui.puts "Duplicated tracer: #{tracer}"
1530
1554
  else
@@ -1724,25 +1748,30 @@ module DEBUGGER__
1724
1748
  file_path, reloaded = @sr.add(iseq, src)
1725
1749
  @ui.event :load, file_path, reloaded
1726
1750
 
1727
- pending_line_breakpoints = @bps.find_all do |key, bp|
1728
- LineBreakpoint === bp && !bp.iseq
1729
- end
1751
+ # check breakpoints
1752
+ if file_path
1753
+ @bps.find_all do |_key, bp|
1754
+ LineBreakpoint === bp && bp.path_is?(file_path) && (iseq.first_lineno..iseq.last_line).cover?(bp.line)
1755
+ end.each do |_key, bp|
1756
+ if !bp.iseq
1757
+ bp.try_activate iseq
1758
+ elsif reloaded
1759
+ @bps.delete bp.key # to allow duplicate
1730
1760
 
1731
- pending_line_breakpoints.each do |_key, bp|
1732
- if DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
1733
- bp.try_activate iseq
1734
- end
1735
- end
1761
+ # When we delete a breakpoint from the @bps hash, we also need to deactivate it or else its tracepoint event
1762
+ # will continue to be enabled and we'll suspend on ghost breakpoints
1763
+ bp.delete
1736
1764
 
1737
- if reloaded
1738
- @bps.find_all do |key, bp|
1739
- LineBreakpoint === bp && DEBUGGER__.compare_path(bp.path, file_path)
1740
- end.each do |_key, bp|
1741
- @bps.delete bp.key # to allow duplicate
1742
- if nbp = LineBreakpoint.copy(bp, iseq)
1765
+ nbp = LineBreakpoint.copy(bp, iseq)
1743
1766
  add_bp nbp
1744
1767
  end
1745
1768
  end
1769
+ else # !file_path => file_path is not existing
1770
+ @bps.find_all do |_key, bp|
1771
+ LineBreakpoint === bp && !bp.iseq && DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
1772
+ end.each do |_key, bp|
1773
+ bp.try_activate iseq
1774
+ end
1746
1775
  end
1747
1776
  end
1748
1777
 
@@ -1980,6 +2009,13 @@ module DEBUGGER__
1980
2009
  def after_fork_parent
1981
2010
  @ui.after_fork_parent
1982
2011
  end
2012
+
2013
+ # experimental API
2014
+ def extend_feature session: nil, thread_client: nil, ui: nil
2015
+ Session.include session if session
2016
+ ThreadClient.include thread_client if thread_client
2017
+ @ui.extend ui if ui
2018
+ end
1983
2019
  end
1984
2020
 
1985
2021
  class ProcessGroup
@@ -2154,6 +2190,7 @@ module DEBUGGER__
2154
2190
  case loc.absolute_path
2155
2191
  when dir_prefix
2156
2192
  when %r{rubygems/core_ext/kernel_require\.rb}
2193
+ when %r{bundled_gems\.rb}
2157
2194
  else
2158
2195
  return loc if loc.absolute_path
2159
2196
  end
@@ -2438,7 +2475,7 @@ module DEBUGGER__
2438
2475
  end
2439
2476
 
2440
2477
  module DaemonInterceptor
2441
- def daemon
2478
+ def daemon(*args)
2442
2479
  return super unless defined?(SESSION) && SESSION.active?
2443
2480
 
2444
2481
  _, child_hook = __fork_setup_for_debugger(:child)
@@ -2504,7 +2541,17 @@ module DEBUGGER__
2504
2541
 
2505
2542
  module TrapInterceptor
2506
2543
  def trap sig, *command, &command_proc
2507
- case sig&.to_sym
2544
+ sym =
2545
+ case sig
2546
+ when String
2547
+ sig.to_sym
2548
+ when Integer
2549
+ Signal.signame(sig)&.to_sym
2550
+ else
2551
+ sig
2552
+ end
2553
+
2554
+ case sym
2508
2555
  when :INT, :SIGINT
2509
2556
  if defined?(SESSION) && SESSION.active? && SESSION.intercept_trap_sigint?
2510
2557
  return SESSION.save_int_trap(command.empty? ? command_proc : command.first)
@@ -22,7 +22,7 @@ module DEBUGGER__
22
22
  end
23
23
  end
24
24
 
25
- if RubyVM.respond_to? :keep_script_lines
25
+ if defined?(RubyVM.keep_script_lines)
26
26
  # Ruby 3.1 and later
27
27
  RubyVM.keep_script_lines = true
28
28
  require 'objspace'
@@ -34,7 +34,7 @@ module DEBUGGER__
34
34
  end
35
35
 
36
36
  def add iseq, src
37
- # do nothing
37
+ # only manage loaded file names
38
38
  if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
39
39
  if @loaded_file_map.has_key? path
40
40
  return path, true # reloaded
@@ -49,7 +49,7 @@ module DEBUGGER__
49
49
  lines = iseq.script_lines&.map(&:chomp)
50
50
  line = iseq.first_line
51
51
  if line > 1
52
- lines = [*([''] * (line - 1)), *lines]
52
+ [*([''] * (line - 1)), *lines]
53
53
  else
54
54
  lines
55
55
  end
@@ -5,6 +5,10 @@ require 'pp'
5
5
 
6
6
  require_relative 'color'
7
7
 
8
+ class ::Thread
9
+ attr_accessor :debug_thread_client
10
+ end
11
+
8
12
  module DEBUGGER__
9
13
  M_INSTANCE_VARIABLES = method(:instance_variables).unbind
10
14
  M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
@@ -14,6 +18,7 @@ module DEBUGGER__
14
18
  M_RESPOND_TO_P = method(:respond_to?).unbind
15
19
  M_METHOD = method(:method).unbind
16
20
  M_OBJECT_ID = method(:object_id).unbind
21
+ M_NAME = method(:name).unbind
17
22
 
18
23
  module SkipPathHelper
19
24
  def skip_path?(path)
@@ -47,12 +52,7 @@ module DEBUGGER__
47
52
 
48
53
  class ThreadClient
49
54
  def self.current
50
- if thc = Thread.current[:DEBUGGER__ThreadClient]
51
- thc
52
- else
53
- thc = SESSION.get_thread_client
54
- Thread.current[:DEBUGGER__ThreadClient] = thc
55
- end
55
+ Thread.current.debug_thread_client ||= SESSION.get_thread_client
56
56
  end
57
57
 
58
58
  include Color
@@ -468,10 +468,14 @@ module DEBUGGER__
468
468
  if file_lines = frame.file_lines
469
469
  frame_line = frame.location.lineno - 1
470
470
 
471
- lines = file_lines.map.with_index do |e, i|
472
- cur = i == frame_line ? '=>' : ' '
473
- line = colorize_dim('%4d|' % (i+1))
474
- "#{cur}#{line} #{e}"
471
+ if CONFIG[:no_lineno]
472
+ lines = file_lines
473
+ else
474
+ lines = file_lines.map.with_index do |e, i|
475
+ cur = i == frame_line ? '=>' : ' '
476
+ line = colorize_dim('%4d|' % (i+1))
477
+ "#{cur}#{line} #{e}"
478
+ end
475
479
  end
476
480
 
477
481
  unless start_line
@@ -607,12 +611,17 @@ module DEBUGGER__
607
611
 
608
612
  def get_consts expr = nil, only_self: false, &block
609
613
  if expr && !expr.empty?
610
- _self = frame_eval(expr)
611
- if M_KIND_OF_P.bind_call(_self, Module)
612
- iter_consts _self, &block
613
- return
614
+ begin
615
+ _self = frame_eval(expr, re_raise: true)
616
+ rescue Exception
617
+ # ignore
614
618
  else
615
- raise "#{_self.inspect} (by #{expr}) is not a Module."
619
+ if M_KIND_OF_P.bind_call(_self, Module)
620
+ iter_consts _self, &block
621
+ return
622
+ else
623
+ puts "#{_self.inspect} (by #{expr}) is not a Module."
624
+ end
616
625
  end
617
626
  elsif _self = current_frame&.self
618
627
  cs = {}
@@ -852,8 +861,18 @@ module DEBUGGER__
852
861
  class SuspendReplay < Exception
853
862
  end
854
863
 
864
+ if ::Fiber.respond_to?(:blocking)
865
+ private def fiber_blocking
866
+ ::Fiber.blocking{yield}
867
+ end
868
+ else
869
+ private def fiber_blocking
870
+ yield
871
+ end
872
+ end
873
+
855
874
  def wait_next_action
856
- wait_next_action_
875
+ fiber_blocking{wait_next_action_}
857
876
  rescue SuspendReplay
858
877
  replay_suspend
859
878
  end
@@ -1038,13 +1057,8 @@ module DEBUGGER__
1038
1057
  when :call
1039
1058
  result = frame_eval(eval_src)
1040
1059
  when :irb
1041
- require 'irb' # prelude's binding.irb doesn't have show_code option
1042
- begin
1043
- result = frame_eval('binding.irb(show_code: false)', binding_location: true)
1044
- ensure
1045
- # workaround: https://github.com/ruby/debug/issues/308
1046
- Reline.prompt_proc = nil if defined? Reline
1047
- end
1060
+ require_relative "irb_integration"
1061
+ activate_irb_integration
1048
1062
  when :display, :try_display
1049
1063
  failed_results = []
1050
1064
  eval_src.each_with_index{|src, i|
@@ -1230,7 +1244,15 @@ module DEBUGGER__
1230
1244
  rescue SuspendReplay, SystemExit, Interrupt
1231
1245
  raise
1232
1246
  rescue Exception => e
1233
- pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
1247
+ STDERR.puts e.cause.inspect
1248
+ STDERR.puts e.inspect
1249
+ Thread.list.each{|th|
1250
+ STDERR.puts "@@@ #{th}"
1251
+ th.backtrace.each{|b|
1252
+ STDERR.puts " > #{b}"
1253
+ }
1254
+ }
1255
+ p ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
1234
1256
  raise
1235
1257
  ensure
1236
1258
  @returning = false
@@ -1292,10 +1314,14 @@ module DEBUGGER__
1292
1314
  frame._callee = b.eval('__callee__')
1293
1315
  end
1294
1316
  }
1295
- @log << frames
1317
+ append(frames)
1296
1318
  }
1297
1319
  end
1298
1320
 
1321
+ def append frames
1322
+ @log << frames
1323
+ end
1324
+
1299
1325
  def enable
1300
1326
  unless @tp_recorder.enabled?
1301
1327
  @log.clear
data/lib/debug/tracer.rb CHANGED
@@ -54,6 +54,10 @@ module DEBUGGER__
54
54
  @tracer.disable
55
55
  end
56
56
 
57
+ def enabled?
58
+ @tracer.enabled?
59
+ end
60
+
57
61
  def description
58
62
  nil
59
63
  end
@@ -85,11 +89,6 @@ module DEBUGGER__
85
89
  end
86
90
  end
87
91
 
88
- def puts msg
89
- @output.puts msg
90
- @output.flush
91
- end
92
-
93
92
  def minfo tp
94
93
  return "block{}" if tp.event == :b_call
95
94
 
data/lib/debug/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DEBUGGER__
4
- VERSION = "1.7.1"
4
+ VERSION = "1.9.1"
5
5
  end