puts_debuggerer 0.9.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.0
1
+ 0.12.0
data/lib/pd.rb ADDED
@@ -0,0 +1 @@
1
+ require 'puts_debuggerer'
@@ -1,33 +1,69 @@
1
- require 'awesome_print' unless RUBY_PLATFORM == 'opal'
2
- require 'stringio'
1
+ require 'puts_debuggerer/core_ext/kernel'
2
+ require 'puts_debuggerer/core_ext/logger'
3
+ require 'puts_debuggerer/core_ext/logging/logger'
4
+ require 'puts_debuggerer/run_determiner'
5
+ require 'puts_debuggerer/source_file'
3
6
 
4
7
  module PutsDebuggerer
5
8
  SOURCE_LINE_COUNT_DEFAULT = 1
6
- HEADER_DEFAULT = '*'*80
9
+ HEADER_DEFAULT = '>'*80
7
10
  WRAPPER_DEFAULT = '*'*80
8
- FOOTER_DEFAULT = '*'*80
11
+ FOOTER_DEFAULT = '<'*80
12
+ LOGGER_FORMATTER_DECORATOR = proc { |original_formatter|
13
+ proc { |severity, datetime, progname, msg|
14
+ original_formatter.call(severity, datetime, progname, msg.pd_inspect)
15
+ }
16
+ }
17
+ LOGGING_LAYOUT_DECORATOR = proc {|original_layout|
18
+ original_layout.clone.tap do |layout|
19
+ layout.singleton_class.class_eval do
20
+ alias original_format_obj format_obj
21
+ def format_obj(obj)
22
+ obj.pdi # alias to pd_inspect
23
+ end
24
+ end
25
+ end
26
+ }
27
+ RETURN_DEFAULT = true
28
+ OBJECT_PRINTER_DEFAULT = lambda do |object, print_engine_options=nil, source_line_count=nil, run_number=nil|
29
+ lambda do
30
+ if object.is_a?(Exception)
31
+ if RUBY_ENGINE == 'opal'
32
+ object.backtrace.each { |line| puts line }
33
+ else
34
+ puts object.full_message
35
+ end
36
+ elsif PutsDebuggerer.print_engine.is_a?(Proc)
37
+ PutsDebuggerer.print_engine.call(object)
38
+ else
39
+ send(PutsDebuggerer.print_engine, object)
40
+ end
41
+ end
42
+ end
9
43
  PRINTER_DEFAULT = :puts
10
44
  PRINTER_RAILS = lambda do |output|
11
45
  puts output if Rails.env.test?
12
46
  Rails.logger.debug(output)
13
47
  end
14
48
  PRINT_ENGINE_DEFAULT = :ap
15
- PRINTER_MESSAGE_INVALID = 'printer must be a valid global method symbol (e.g. :puts) or lambda/proc receiving a text arg'
49
+ PRINTER_MESSAGE_INVALID = 'printer must be a valid global method symbol (e.g. :puts), a logger, or a lambda/proc receiving a text arg'
16
50
  PRINT_ENGINE_MESSAGE_INVALID = 'print_engine must be a valid global method symbol (e.g. :p, :ap or :pp) or lambda/proc receiving an object arg'
17
51
  ANNOUNCER_DEFAULT = '[PD]'
18
52
  FORMATTER_DEFAULT = -> (data) {
19
53
  puts data[:wrapper] if data[:wrapper]
20
54
  puts data[:header] if data[:header]
21
- print "#{data[:announcer]} #{data[:file]}:#{data[:line_number]}#{" (run:#{data[:run_number]})" if data[:run_number]}#{__format_pd_expression__(data[:pd_expression], data[:object])} "
55
+ print "#{data[:announcer]} #{data[:file]}#{':' if data[:line_number]}#{data[:line_number]}#{" (run:#{data[:run_number]})" if data[:run_number]}#{__format_pd_expression__(data[:pd_expression], data[:object])} "
22
56
  data[:object_printer].call
23
57
  puts data[:caller].map {|l| ' ' + l} unless data[:caller].to_a.empty?
24
58
  puts data[:footer] if data[:footer]
25
59
  puts data[:wrapper] if data[:wrapper]
26
60
  }
27
61
  CALLER_DEPTH_ZERO = 4 #depth includes pd + with_options method + nested block + build_pd_data method
28
- OBJECT_RUN_AT = {}
62
+ CALLER_DEPTH_ZERO_OPAL = -1 #depth includes pd + with_options method + nested block + build_pd_data method
29
63
  STACK_TRACE_CALL_LINE_NUMBER_REGEX = /\:(\d+)\:in /
30
64
  STACK_TRACE_CALL_SOURCE_FILE_REGEX = /[ ]*([^:]+)\:\d+\:in /
65
+ STACK_TRACE_CALL_SOURCE_FILE_REGEX_OPAL = /(http[^\)]+)/
66
+ OPTIONS = [:app_path, :source_line_count, :header, :wrapper, :footer, :printer, :print_engine, :announcer, :formatter, :caller, :run_at]
31
67
 
32
68
  class << self
33
69
  # Application root path to exclude when printing out file path
@@ -90,20 +126,6 @@ module PutsDebuggerer
90
126
  # => "1"
91
127
  attr_reader :header
92
128
 
93
- def header=(value)
94
- if value.equal?(true)
95
- @header = HEADER_DEFAULT
96
- elsif value == ''
97
- @header = nil
98
- else
99
- @header = value
100
- end
101
- end
102
-
103
- def header?
104
- !!@header
105
- end
106
-
107
129
  # Wrapper to include at the top and bottom of every print out (both header and footer).
108
130
  # * Default value is `nil`
109
131
  # * Value `true` enables wrapper as `'*'*80`
@@ -123,20 +145,6 @@ module PutsDebuggerer
123
145
  # ********************************************************************************
124
146
  attr_reader :wrapper
125
147
 
126
- def wrapper=(value)
127
- if value.equal?(true)
128
- @wrapper = WRAPPER_DEFAULT
129
- elsif value == ''
130
- @wrapper = nil
131
- else
132
- @wrapper = value
133
- end
134
- end
135
-
136
- def wrapper?
137
- !!@wrapper
138
- end
139
-
140
148
  # Footer to include at the bottom of every print out.
141
149
  # * Default value is `nil`
142
150
  # * Value `true` enables footer as `'*'*80`
@@ -155,24 +163,27 @@ module PutsDebuggerer
155
163
  # => "1"
156
164
  # ********************************************************************************
157
165
  attr_reader :footer
158
-
159
- def footer=(value)
160
- if value.equal?(true)
161
- @footer = FOOTER_DEFAULT
162
- elsif value == ''
163
- @footer = nil
164
- else
165
- @footer = value
166
+
167
+ ['header', 'footer', 'wrapper'].each do |boundary_option|
168
+ define_method("#{boundary_option}=") do |value|
169
+ if value.equal?(true)
170
+ instance_variable_set(:"@#{boundary_option}", const_get(:"#{boundary_option.upcase}_DEFAULT"))
171
+ elsif value == ''
172
+ instance_variable_set(:"@#{boundary_option}", nil)
173
+ else
174
+ instance_variable_set(:"@#{boundary_option}", value)
175
+ end
176
+ end
177
+
178
+ define_method("#{boundary_option}?") do
179
+ !!instance_variable_get(:"@#{boundary_option}")
166
180
  end
167
181
  end
168
182
 
169
- def footer?
170
- !!@footer
171
- end
172
-
173
- # Printer is a global method symbol or lambda expression to use in printing to the user.
174
- # Examples of global methods are `:puts` and `:print`.
183
+ # Printer is a global method symbol, lambda expression, or logger to use in printing to the user.
184
+ # Examples of a global method are `:puts` and `:print`.
175
185
  # An example of a lambda expression is `lambda {|output| Rails.logger.ap(output)}`
186
+ # Examples of a logger are a Ruby `Logger` instance or `Logging::Logger` instance
176
187
  #
177
188
  # Defaults to `:puts`
178
189
  # In Rails, it defaults to: `lambda {|output| Rails.logger.ap(output)}`
@@ -193,17 +204,37 @@ module PutsDebuggerer
193
204
 
194
205
  def printer=(printer)
195
206
  if printer.nil?
196
- if Object.const_defined?(:Rails)
197
- @printer = PRINTER_RAILS
198
- else
199
- @printer = PRINTER_DEFAULT
207
+ @printer = printer_default
208
+ elsif printer.is_a?(Logger)
209
+ @printer = printer
210
+ @logger_original_formatter = printer.formatter || Logger::Formatter.new
211
+ printer.formatter = LOGGER_FORMATTER_DECORATOR.call(@logger_original_formatter)
212
+ elsif printer.is_a?(Logging::Logger)
213
+ @printer = printer
214
+ @logging_original_layouts = printer.appenders.reduce({}) do |hash, appender|
215
+ hash.merge(appender => appender.layout)
200
216
  end
201
- elsif printer.is_a?(Proc)
217
+ printer.appenders.each do |appender|
218
+ appender.layout = LOGGING_LAYOUT_DECORATOR.call(appender.layout)
219
+ end
220
+ elsif printer == false || printer.is_a?(Proc) || printer.respond_to?(:log) # a logger
202
221
  @printer = printer
203
222
  else
204
223
  @printer = method(printer).name rescue raise(PRINTER_MESSAGE_INVALID)
205
224
  end
206
225
  end
226
+
227
+ def printer_default
228
+ Object.const_defined?(:Rails) ? PRINTER_RAILS : PRINTER_DEFAULT
229
+ end
230
+
231
+ # Logger original formatter before it was decorated with PutsDebuggerer::LOGGER_FORMATTER_DECORATOR
232
+ # upon setting the logger as a printer.
233
+ attr_reader :logger_original_formatter
234
+
235
+ # Logging library original layouts before being decorated with PutsDebuggerer::LOGGING_LAYOUT_DECORATOR
236
+ # upon setting the Logging library logger as a printer.
237
+ attr_reader :logging_original_layouts
207
238
 
208
239
  # Print engine is similar to `printer`, except it is focused on the scope of formatting
209
240
  # the data object being printed (excluding metadata such as file name, line number,
@@ -228,17 +259,25 @@ module PutsDebuggerer
228
259
  # > pd array
229
260
  # => [1, [2, 3]]
230
261
  # ]
231
- attr_reader :print_engine
262
+ def print_engine
263
+ if @print_engine.nil?
264
+ require 'awesome_print' if RUBY_ENGINE != 'opal'
265
+ @print_engine = print_engine_default
266
+ end
267
+ @print_engine
268
+ end
232
269
 
233
270
  def print_engine=(engine)
234
- if engine.nil?
235
- @print_engine = Object.const_defined?(:AwesomePrint) ? PRINT_ENGINE_DEFAULT : :p
236
- elsif engine.is_a?(Proc)
271
+ if engine.is_a?(Proc) || engine.nil?
237
272
  @print_engine = engine
238
273
  else
239
274
  @print_engine = method(engine).name rescue raise(PRINT_ENGINE_MESSAGE_INVALID)
240
275
  end
241
276
  end
277
+
278
+ def print_engine_default
279
+ Object.const_defined?(:AwesomePrint) ? PRINT_ENGINE_DEFAULT : :p
280
+ end
242
281
 
243
282
  # Announcer (e.g. [PD]) to announce every print out with (default: "[PD]")
244
283
  #
@@ -430,44 +469,32 @@ module PutsDebuggerer
430
469
  !!@run_at
431
470
  end
432
471
 
433
- attr_reader :run_at_global_number
434
-
435
- def run_at_global_number=(value)
436
- @run_at_global_number = value
437
- end
438
-
439
- def init_run_at_global_number
440
- @run_at_global_number = 1
441
- end
442
-
443
- def increment_run_at_global_number
444
- @run_at_global_number += 1
445
- end
446
-
447
- def reset_run_at_global_number
448
- @run_at_global_number = nil
449
- end
450
-
451
- def run_at_number(object, run_at)
452
- PutsDebuggerer::OBJECT_RUN_AT[[object,run_at]]
453
- end
454
-
455
- def init_run_at_number(object, run_at)
456
- PutsDebuggerer::OBJECT_RUN_AT[[object,run_at]] = 1
472
+ def determine_options(objects)
473
+ if objects.size > 1 && objects.last.is_a?(Hash)
474
+ objects.delete_at(-1)
475
+ elsif objects.size == 1 && objects.first.is_a?(Hash)
476
+ hash = objects.first
477
+ hash.slice(*OPTIONS).tap do
478
+ hash.delete_if {|option| OPTIONS.include?(option)}
479
+ end
480
+ end
457
481
  end
458
482
 
459
- def increment_run_at_number(object, run_at)
460
- PutsDebuggerer::OBJECT_RUN_AT[[object,run_at]] += 1
483
+ def determine_object(objects)
484
+ objects.compact.size > 1 ? objects : objects.first
461
485
  end
462
486
 
463
- def reset_run_at_number(object, run_at)
464
- PutsDebuggerer::OBJECT_RUN_AT.delete([object, run_at])
487
+ def determine_run_at(options)
488
+ ((options && options[:run_at]) || PutsDebuggerer.run_at)
465
489
  end
466
490
 
467
- def reset_run_at_numbers
468
- PutsDebuggerer::OBJECT_RUN_AT.clear
491
+ def determine_printer(options)
492
+ if options && options.has_key?(:printer)
493
+ options[:printer]
494
+ else
495
+ PutsDebuggerer.printer
496
+ end
469
497
  end
470
-
471
498
  end
472
499
  end
473
500
 
@@ -480,219 +507,3 @@ PutsDebuggerer.app_path = nil
480
507
  PutsDebuggerer.caller = nil
481
508
  PutsDebuggerer.run_at = nil
482
509
  PutsDebuggerer.source_line_count = nil
483
-
484
- # Prints object with bonus info such as file name, line number and source
485
- # expression. Optionally prints out header and footer.
486
- # Lookup PutsDebuggerer attributes for more details about configuration options.
487
- #
488
- # Simply invoke global `pd` method anywhere you'd like to see line number and source code with output.
489
- # If the argument is a pure string, the print out is simplified by not showing duplicate source.
490
- #
491
- # Quickly locate printed lines using Find feature (e.g. CTRL+F) by looking for:
492
- # * \[PD\]
493
- # * file:line_number
494
- # * ruby expression.
495
- #
496
- # This gives you the added benefit of easily removing your pd statements later on from the code.
497
- #
498
- # Happy puts_debuggerering!
499
- #
500
- # Example Code:
501
- #
502
- # # /Users/User/finance_calculator_app/pd_test.rb # line 1
503
- # bug = 'beattle' # line 2
504
- # pd "Show me the source of the bug: #{bug}" # line 3
505
- # pd 'What line number am I?' # line 4
506
- #
507
- # Example Printout:
508
- #
509
- # [PD] /Users/User/finance_calculator_app/pd_test.rb:3
510
- # > pd "Show me the source of the bug: #{bug}"
511
- # => "Show me the source of the bug: beattle"
512
- # [PD] /Users/User/finance_calculator_app/pd_test.rb:4 "What line number am I?"
513
- def pd(*objects)
514
- options = objects.delete_at(-1) if objects.size > 1 && objects.last.is_a?(Hash)
515
- object = objects.compact.size > 1 ? objects : objects.first
516
- run_at = ((options && options[:run_at]) || PutsDebuggerer.run_at)
517
-
518
- if __run_pd__(object, run_at)
519
- __with_pd_options__(options) do |print_engine_options|
520
- run_number = PutsDebuggerer.run_at_global_number || PutsDebuggerer.run_at_number(object, run_at)
521
- formatter_pd_data = __build_pd_data__(object, print_engine_options, PutsDebuggerer.source_line_count, run_number) #depth adds build method
522
- stdout = $stdout
523
- $stdout = sio = StringIO.new
524
- PutsDebuggerer.formatter.call(formatter_pd_data)
525
- $stdout = stdout
526
- if PutsDebuggerer.printer.is_a?(Proc)
527
- PutsDebuggerer.printer.call(sio.string)
528
- else
529
- send(PutsDebuggerer.send(:printer), sio.string)
530
- end
531
- end
532
- end
533
-
534
- object
535
- end
536
-
537
- def __run_pd__(object, run_at)
538
- run_pd = false
539
- if run_at.nil?
540
- run_pd = true
541
- else
542
- if PutsDebuggerer.run_at?
543
- if PutsDebuggerer.run_at_global_number.nil?
544
- PutsDebuggerer.init_run_at_global_number
545
- else
546
- PutsDebuggerer.increment_run_at_global_number
547
- end
548
- run_number = PutsDebuggerer.run_at_global_number
549
- else
550
- if PutsDebuggerer.run_at_number(object, run_at).nil?
551
- PutsDebuggerer.init_run_at_number(object, run_at)
552
- else
553
- PutsDebuggerer.increment_run_at_number(object, run_at)
554
- end
555
- run_number = PutsDebuggerer.run_at_number(object, run_at)
556
- end
557
- if run_at.is_a?(Integer)
558
- run_pd = true if run_at == run_number
559
- elsif run_at.is_a?(Array)
560
- run_pd = true if run_at.include?(run_number)
561
- elsif run_at.is_a?(Range)
562
- run_pd = true if run_at.cover?(run_number) || (run_at.end == -1 && run_number >= run_at.begin)
563
- end
564
- end
565
- run_pd
566
- end
567
-
568
- # Provides caller line number starting 1 level above caller of
569
- # this method.
570
- #
571
- # Example:
572
- #
573
- # # lib/example.rb # line 1
574
- # puts "Print out __caller_line_number__" # line 2
575
- # puts __caller_line_number__ # line 3
576
- #
577
- # prints out `3`
578
- def __caller_line_number__(caller_depth=0)
579
- caller[caller_depth] && caller[caller_depth][PutsDebuggerer::STACK_TRACE_CALL_LINE_NUMBER_REGEX, 1].to_i
580
- end
581
-
582
- # Provides caller file starting 1 level above caller of
583
- # this method.
584
- #
585
- # Example:
586
- #
587
- # # File Name: lib/example.rb
588
- # puts __caller_file__
589
- #
590
- # prints out `lib/example.rb`
591
- def __caller_file__(caller_depth=0)
592
- caller[caller_depth] && caller[caller_depth][PutsDebuggerer::STACK_TRACE_CALL_SOURCE_FILE_REGEX, 1]
593
- end
594
-
595
-
596
- # Provides caller source line starting 1 level above caller of
597
- # this method.
598
- #
599
- # Example:
600
- #
601
- # puts __caller_source_line__
602
- #
603
- # prints out `puts __caller_source_line__`
604
- def __caller_source_line__(caller_depth=0, source_line_count=nil, source_file=nil, source_line_number=nil)
605
- source_line_number ||= __caller_line_number__(caller_depth+1)
606
- source_file ||= __caller_file__(caller_depth+1)
607
- source_line = ''
608
- if source_file == '(irb)'
609
- source_line = conf.io.line(source_line_number)
610
- else
611
- f = File.new(source_file)
612
- if f.respond_to?(:readline) # Opal Ruby Compatibility
613
- source_lines = []
614
- begin
615
- while f.lineno < source_line_number + source_line_count
616
- file_line_number = f.lineno + 1
617
- file_line = f.readline
618
- if file_line_number >= source_line_number && file_line_number < source_line_number + source_line_count
619
- source_lines << file_line
620
- end
621
- end
622
- rescue EOFError
623
- # Done
624
- end
625
- source_line = source_lines.join(' '*5)
626
- end
627
- end
628
- source_line
629
- end
630
-
631
- private
632
-
633
- def __with_pd_options__(options=nil)
634
- options ||= {}
635
- permanent_options = PutsDebuggerer.options
636
- PutsDebuggerer.options = options.select {|option, _| PutsDebuggerer.options.keys.include?(option)}
637
- print_engine_options = options.delete_if {|option, _| PutsDebuggerer.options.keys.include?(option)}
638
- yield print_engine_options
639
- PutsDebuggerer.options = permanent_options
640
- end
641
-
642
- def __build_pd_data__(object, print_engine_options=nil, source_line_count=nil, run_number=nil)
643
- depth = PutsDebuggerer::CALLER_DEPTH_ZERO
644
- pd_data = {
645
- announcer: PutsDebuggerer.announcer,
646
- file: __caller_file__(depth)&.sub(PutsDebuggerer.app_path.to_s, ''),
647
- line_number: __caller_line_number__(depth),
648
- pd_expression: __caller_pd_expression__(depth, source_line_count),
649
- run_number: run_number,
650
- object: object,
651
- object_printer: lambda do
652
- if object.is_a?(Exception) && object.respond_to?(:full_message)
653
- puts object.full_message
654
- elsif PutsDebuggerer.print_engine.is_a?(Proc)
655
- PutsDebuggerer.print_engine.call(object)
656
- else
657
- if print_engine_options.to_h.empty?
658
- send(PutsDebuggerer.print_engine, object)
659
- else
660
- send(PutsDebuggerer.print_engine, object, print_engine_options) rescue send(PutsDebuggerer.print_engine, object)
661
- end
662
- end
663
- end
664
- }
665
- if PutsDebuggerer.caller?
666
- start_depth = depth.to_i
667
- caller_depth = PutsDebuggerer.caller == -1 ? -1 : (start_depth + PutsDebuggerer.caller)
668
- pd_data[:caller] = caller[start_depth..caller_depth].to_a
669
- end
670
- pd_data[:header] = PutsDebuggerer.header if PutsDebuggerer.header?
671
- pd_data[:wrapper] = PutsDebuggerer.wrapper if PutsDebuggerer.wrapper?
672
- pd_data[:footer] = PutsDebuggerer.footer if PutsDebuggerer.footer?
673
- pd_data
674
- end
675
-
676
- def __format_pd_expression__(expression, object)
677
- "\n > #{expression}\n =>"
678
- end
679
-
680
- def __caller_pd_expression__(depth=0, source_line_count=nil)
681
- # Caller Source Line Depth 2 = 1 to pd method + 1 to caller
682
- source_line = __caller_source_line__(depth+1, source_line_count)
683
- source_line = __extract_pd_expression__(source_line)
684
- source_line = source_line.gsub(/(^'|'$)/, '"') if source_line.start_with?("'") && source_line.end_with?("'")
685
- source_line = source_line.gsub(/(^\(|\)$)/, '') if source_line.start_with?("(") && source_line.end_with?(")")
686
- source_line
687
- end
688
-
689
- # Extracts pd source line expression.
690
- #
691
- # Example:
692
- #
693
- # __extract_pd_expression__("pd (x=1)")
694
- #
695
- # outputs `(x=1)`
696
- def __extract_pd_expression__(source_line)
697
- source_line.to_s.strip
698
- end