puts_debuggerer 0.8.2 → 0.11.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.8.2
1
+ 0.11.0
@@ -0,0 +1 @@
1
+ require 'puts_debuggerer'
@@ -1,30 +1,68 @@
1
- require 'ripper'
2
- require 'awesome_print'
3
- 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'
4
6
 
5
7
  module PutsDebuggerer
6
- HEADER_DEFAULT = '*'*80
7
- FOOTER_DEFAULT = '*'*80
8
+ SOURCE_LINE_COUNT_DEFAULT = 1
9
+ HEADER_DEFAULT = '>'*80
10
+ WRAPPER_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
8
43
  PRINTER_DEFAULT = :puts
9
44
  PRINTER_RAILS = lambda do |output|
10
45
  puts output if Rails.env.test?
11
46
  Rails.logger.debug(output)
12
47
  end
13
48
  PRINT_ENGINE_DEFAULT = :ap
14
- 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'
15
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'
16
51
  ANNOUNCER_DEFAULT = '[PD]'
17
52
  FORMATTER_DEFAULT = -> (data) {
53
+ puts data[:wrapper] if data[:wrapper]
18
54
  puts data[:header] if data[:header]
19
- print "#{data[:announcer]} #{data[:file]}:#{data[:line_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])} "
20
56
  data[:object_printer].call
21
57
  puts data[:caller].map {|l| ' ' + l} unless data[:caller].to_a.empty?
22
58
  puts data[:footer] if data[:footer]
59
+ puts data[:wrapper] if data[:wrapper]
23
60
  }
24
61
  CALLER_DEPTH_ZERO = 4 #depth includes pd + with_options method + nested block + build_pd_data method
25
- OBJECT_RUN_AT = {}
62
+ CALLER_DEPTH_ZERO_OPAL = -1 #depth includes pd + with_options method + nested block + build_pd_data method
26
63
  STACK_TRACE_CALL_LINE_NUMBER_REGEX = /\:(\d+)\:in /
27
64
  STACK_TRACE_CALL_SOURCE_FILE_REGEX = /[ ]*([^:]+)\:\d+\:in /
65
+ STACK_TRACE_CALL_SOURCE_FILE_REGEX_OPAL = /(http[^\)]+)/
28
66
 
29
67
  class << self
30
68
  # Application root path to exclude when printing out file path
@@ -46,6 +84,28 @@ module PutsDebuggerer
46
84
  @app_path = (path || Rails.root.to_s) rescue nil
47
85
  end
48
86
 
87
+ # Source Line Count.
88
+ # * Default value is `1`
89
+ #
90
+ # Example:
91
+ #
92
+ # PutsDebuggerer.source_line_count = 2
93
+ # pd (true ||
94
+ # false), source_line_count: 2
95
+ #
96
+ # Prints out:
97
+ #
98
+ # ********************************************************************************
99
+ # [PD] /Users/User/example.rb:2
100
+ # > pd (true ||
101
+ # false), source_line_count: 2
102
+ # => "true"
103
+ attr_reader :source_line_count
104
+
105
+ def source_line_count=(value)
106
+ @source_line_count = value || SOURCE_LINE_COUNT_DEFAULT
107
+ end
108
+
49
109
  # Header to include at the top of every print out.
50
110
  # * Default value is `nil`
51
111
  # * Value `true` enables header as `'*'*80`
@@ -65,19 +125,24 @@ module PutsDebuggerer
65
125
  # => "1"
66
126
  attr_reader :header
67
127
 
68
- def header=(value)
69
- if value.equal?(true)
70
- @header = HEADER_DEFAULT
71
- elsif value == ''
72
- @header = nil
73
- else
74
- @header = value
75
- end
76
- end
77
-
78
- def header?
79
- !!@header
80
- end
128
+ # Wrapper to include at the top and bottom of every print out (both header and footer).
129
+ # * Default value is `nil`
130
+ # * Value `true` enables wrapper as `'*'*80`
131
+ # * Value `false`, `nil`, or empty string disables wrapper
132
+ # * Any other string value gets set as a custom wrapper
133
+ #
134
+ # Example:
135
+ #
136
+ # PutsDebuggerer.wrapper = true
137
+ # pd (x=1)
138
+ #
139
+ # Prints out:
140
+ #
141
+ # [PD] /Users/User/example.rb:2
142
+ # > pd x=1
143
+ # => "1"
144
+ # ********************************************************************************
145
+ attr_reader :wrapper
81
146
 
82
147
  # Footer to include at the bottom of every print out.
83
148
  # * Default value is `nil`
@@ -97,24 +162,27 @@ module PutsDebuggerer
97
162
  # => "1"
98
163
  # ********************************************************************************
99
164
  attr_reader :footer
100
-
101
- def footer=(value)
102
- if value.equal?(true)
103
- @footer = FOOTER_DEFAULT
104
- elsif value == ''
105
- @footer = nil
106
- else
107
- @footer = value
165
+
166
+ ['header', 'footer', 'wrapper'].each do |boundary_option|
167
+ define_method("#{boundary_option}=") do |value|
168
+ if value.equal?(true)
169
+ instance_variable_set(:"@#{boundary_option}", const_get(:"#{boundary_option.upcase}_DEFAULT"))
170
+ elsif value == ''
171
+ instance_variable_set(:"@#{boundary_option}", nil)
172
+ else
173
+ instance_variable_set(:"@#{boundary_option}", value)
174
+ end
175
+ end
176
+
177
+ define_method("#{boundary_option}?") do
178
+ !!instance_variable_get(:"@#{boundary_option}")
108
179
  end
109
180
  end
110
181
 
111
- def footer?
112
- !!@footer
113
- end
114
-
115
- # Printer is a global method symbol or lambda expression to use in printing to the user.
116
- # Examples of global methods are `:puts` and `:print`.
182
+ # Printer is a global method symbol, lambda expression, or logger to use in printing to the user.
183
+ # Examples of a global method are `:puts` and `:print`.
117
184
  # An example of a lambda expression is `lambda {|output| Rails.logger.ap(output)}`
185
+ # Examples of a logger are a Ruby `Logger` instance or `Logging::Logger` instance
118
186
  #
119
187
  # Defaults to `:puts`
120
188
  # In Rails, it defaults to: `lambda {|output| Rails.logger.ap(output)}`
@@ -135,17 +203,37 @@ module PutsDebuggerer
135
203
 
136
204
  def printer=(printer)
137
205
  if printer.nil?
138
- if Object.const_defined?(:Rails)
139
- @printer = PRINTER_RAILS
140
- else
141
- @printer = PRINTER_DEFAULT
206
+ @printer = printer_default
207
+ elsif printer.is_a?(Logger)
208
+ @printer = printer
209
+ @logger_original_formatter = printer.formatter || Logger::Formatter.new
210
+ printer.formatter = LOGGER_FORMATTER_DECORATOR.call(@logger_original_formatter)
211
+ elsif printer.is_a?(Logging::Logger)
212
+ @printer = printer
213
+ @logging_original_layouts = printer.appenders.reduce({}) do |hash, appender|
214
+ hash.merge(appender => appender.layout)
215
+ end
216
+ printer.appenders.each do |appender|
217
+ appender.layout = LOGGING_LAYOUT_DECORATOR.call(appender.layout)
142
218
  end
143
- elsif printer.is_a?(Proc)
219
+ elsif printer == false || printer.is_a?(Proc) || printer.respond_to?(:log) # a logger
144
220
  @printer = printer
145
221
  else
146
222
  @printer = method(printer).name rescue raise(PRINTER_MESSAGE_INVALID)
147
223
  end
148
224
  end
225
+
226
+ def printer_default
227
+ Object.const_defined?(:Rails) ? PRINTER_RAILS : PRINTER_DEFAULT
228
+ end
229
+
230
+ # Logger original formatter before it was decorated with PutsDebuggerer::LOGGER_FORMATTER_DECORATOR
231
+ # upon setting the logger as a printer.
232
+ attr_reader :logger_original_formatter
233
+
234
+ # Logging library original layouts before being decorated with PutsDebuggerer::LOGGING_LAYOUT_DECORATOR
235
+ # upon setting the Logging library logger as a printer.
236
+ attr_reader :logging_original_layouts
149
237
 
150
238
  # Print engine is similar to `printer`, except it is focused on the scope of formatting
151
239
  # the data object being printed (excluding metadata such as file name, line number,
@@ -170,17 +258,25 @@ module PutsDebuggerer
170
258
  # > pd array
171
259
  # => [1, [2, 3]]
172
260
  # ]
173
- attr_reader :print_engine
261
+ def print_engine
262
+ if @print_engine.nil?
263
+ require 'awesome_print' if RUBY_ENGINE != 'opal'
264
+ @print_engine = print_engine_default
265
+ end
266
+ @print_engine
267
+ end
174
268
 
175
269
  def print_engine=(engine)
176
- if engine.nil?
177
- @print_engine = PRINT_ENGINE_DEFAULT
178
- elsif engine.is_a?(Proc)
270
+ if engine.is_a?(Proc) || engine.nil?
179
271
  @print_engine = engine
180
272
  else
181
273
  @print_engine = method(engine).name rescue raise(PRINT_ENGINE_MESSAGE_INVALID)
182
274
  end
183
275
  end
276
+
277
+ def print_engine_default
278
+ Object.const_defined?(:AwesomePrint) ? PRINT_ENGINE_DEFAULT : :p
279
+ end
184
280
 
185
281
  # Announcer (e.g. [PD]) to announce every print out with (default: "[PD]")
186
282
  #
@@ -206,12 +302,14 @@ module PutsDebuggerer
206
302
  # * :announcer (string)
207
303
  # * :caller (array)
208
304
  # * :file (string)
305
+ # * :wrapper (string)
209
306
  # * :footer (string)
210
307
  # * :header (string)
211
308
  # * :line_number (string)
212
309
  # * :pd_expression (string)
213
310
  # * :object (object)
214
311
  # * :object_printer (proc)
312
+ # * :source_line_count (integer)
215
313
  #
216
314
  # NOTE: data for :object_printer is not a string, yet a proc that must
217
315
  # be called to output value. It is a proc as it automatically handles usage
@@ -289,9 +387,11 @@ module PutsDebuggerer
289
387
  def options
290
388
  {
291
389
  header: header,
390
+ wrapper: wrapper,
292
391
  footer: footer,
293
392
  printer: printer,
294
393
  print_engine: print_engine,
394
+ source_line_count: source_line_count,
295
395
  app_path: app_path,
296
396
  announcer: announcer,
297
397
  formatter: formatter,
@@ -368,44 +468,25 @@ module PutsDebuggerer
368
468
  !!@run_at
369
469
  end
370
470
 
371
- attr_reader :run_at_global_number
372
-
373
- def run_at_global_number=(value)
374
- @run_at_global_number = value
375
- end
376
-
377
- def init_run_at_global_number
378
- @run_at_global_number = 1
379
- end
380
-
381
- def increment_run_at_global_number
382
- @run_at_global_number += 1
383
- end
384
-
385
- def reset_run_at_global_number
386
- @run_at_global_number = nil
387
- end
388
-
389
- def run_at_number(object, run_at)
390
- PutsDebuggerer::OBJECT_RUN_AT[[object,run_at]]
471
+ def determine_options(objects)
472
+ objects.delete_at(-1) if objects.size > 1 && objects.last.is_a?(Hash)
391
473
  end
392
474
 
393
- def init_run_at_number(object, run_at)
394
- PutsDebuggerer::OBJECT_RUN_AT[[object,run_at]] = 1
475
+ def determine_object(objects)
476
+ objects.compact.size > 1 ? objects : objects.first
395
477
  end
396
478
 
397
- def increment_run_at_number(object, run_at)
398
- PutsDebuggerer::OBJECT_RUN_AT[[object,run_at]] += 1
479
+ def determine_run_at(options)
480
+ ((options && options[:run_at]) || PutsDebuggerer.run_at)
399
481
  end
400
482
 
401
- def reset_run_at_number(object, run_at)
402
- PutsDebuggerer::OBJECT_RUN_AT.delete([object, run_at])
403
- end
404
-
405
- def reset_run_at_numbers
406
- PutsDebuggerer::OBJECT_RUN_AT.clear
483
+ def determine_printer(options)
484
+ if options && options.has_key?(:printer)
485
+ options[:printer]
486
+ else
487
+ PutsDebuggerer.printer
488
+ end
407
489
  end
408
-
409
490
  end
410
491
  end
411
492
 
@@ -417,206 +498,4 @@ PutsDebuggerer.formatter = nil
417
498
  PutsDebuggerer.app_path = nil
418
499
  PutsDebuggerer.caller = nil
419
500
  PutsDebuggerer.run_at = nil
420
-
421
- # Prints object with bonus info such as file name, line number and source
422
- # expression. Optionally prints out header and footer.
423
- # Lookup PutsDebuggerer attributes for more details about configuration options.
424
- #
425
- # Simply invoke global `pd` method anywhere you'd like to see line number and source code with output.
426
- # If the argument is a pure string, the print out is simplified by not showing duplicate source.
427
- #
428
- # Quickly locate printed lines using Find feature (e.g. CTRL+F) by looking for:
429
- # * \[PD\]
430
- # * file:line_number
431
- # * ruby expression.
432
- #
433
- # This gives you the added benefit of easily removing your pd statements later on from the code.
434
- #
435
- # Happy puts_debuggerering!
436
- #
437
- # Example Code:
438
- #
439
- # # /Users/User/finance_calculator_app/pd_test.rb # line 1
440
- # bug = 'beattle' # line 2
441
- # pd "Show me the source of the bug: #{bug}" # line 3
442
- # pd 'What line number am I?' # line 4
443
- #
444
- # Example Printout:
445
- #
446
- # [PD] /Users/User/finance_calculator_app/pd_test.rb:3
447
- # > pd "Show me the source of the bug: #{bug}"
448
- # => "Show me the source of the bug: beattle"
449
- # [PD] /Users/User/finance_calculator_app/pd_test.rb:4 "What line number am I?"
450
- def pd(object, options=nil)
451
- run_at = ((options && options[:run_at]) || PutsDebuggerer.run_at)
452
-
453
- if __run_pd__(object, run_at)
454
- __with_pd_options__(options) do |print_engine_options|
455
- formatter_pd_data = __build_pd_data__(object, print_engine_options) #depth adds build method
456
- stdout = $stdout
457
- $stdout = sio = StringIO.new
458
- PutsDebuggerer.formatter.call(formatter_pd_data)
459
- $stdout = stdout
460
- if PutsDebuggerer.printer.is_a?(Proc)
461
- PutsDebuggerer.printer.call(sio.string)
462
- else
463
- send(PutsDebuggerer.send(:printer), sio.string)
464
- end
465
- end
466
- end
467
-
468
- object
469
- end
470
-
471
- def __run_pd__(object, run_at)
472
- run_pd = false
473
- if run_at.nil?
474
- run_pd = true
475
- else
476
- if PutsDebuggerer.run_at?
477
- if PutsDebuggerer.run_at_global_number.nil?
478
- PutsDebuggerer.init_run_at_global_number
479
- else
480
- PutsDebuggerer.increment_run_at_global_number
481
- end
482
- run_number = PutsDebuggerer.run_at_global_number
483
- else
484
- if PutsDebuggerer.run_at_number(object, run_at).nil?
485
- PutsDebuggerer.init_run_at_number(object, run_at)
486
- else
487
- PutsDebuggerer.increment_run_at_number(object, run_at)
488
- end
489
- run_number = PutsDebuggerer.run_at_number(object, run_at)
490
- end
491
- if run_at.is_a?(Integer)
492
- run_pd = true if run_at == run_number
493
- elsif run_at.is_a?(Array)
494
- run_pd = true if run_at.include?(run_number)
495
- elsif run_at.is_a?(Range)
496
- run_pd = true if run_at.cover?(run_number) || (run_at.end == -1 && run_number >= run_at.begin)
497
- end
498
- end
499
- run_pd
500
- end
501
-
502
- # Provides caller line number starting 1 level above caller of
503
- # this method.
504
- #
505
- # Example:
506
- #
507
- # # lib/example.rb # line 1
508
- # puts "Print out __caller_line_number__" # line 2
509
- # puts __caller_line_number__ # line 3
510
- #
511
- # prints out `3`
512
- def __caller_line_number__(caller_depth=0)
513
- caller[caller_depth][PutsDebuggerer::STACK_TRACE_CALL_LINE_NUMBER_REGEX, 1].to_i
514
- end
515
-
516
- # Provides caller file starting 1 level above caller of
517
- # this method.
518
- #
519
- # Example:
520
- #
521
- # # File Name: lib/example.rb
522
- # puts __caller_file__
523
- #
524
- # prints out `lib/example.rb`
525
- def __caller_file__(caller_depth=0)
526
- caller[caller_depth][PutsDebuggerer::STACK_TRACE_CALL_SOURCE_FILE_REGEX, 1]
527
- end
528
-
529
-
530
- # Provides caller source line starting 1 level above caller of
531
- # this method.
532
- #
533
- # Example:
534
- #
535
- # puts __caller_source_line__
536
- #
537
- # prints out `puts __caller_source_line__`
538
- def __caller_source_line__(caller_depth=0, source_file=nil, source_line_number=nil)
539
- source_line_number ||= __caller_line_number__(caller_depth+1)
540
- source_file ||= __caller_file__(caller_depth+1)
541
- source_line = nil
542
- if source_file == '(irb)'
543
- source_line = conf.io.line(source_line_number)
544
- else
545
- f = File.new(source_file)
546
- source_line = ''
547
- done = false
548
- f.each_line do |line|
549
- if !done && f.lineno == source_line_number
550
- source_line << line
551
- done = true if Ripper.sexp_raw(source_line) || source_line.include?('%>') #the last condition is for erb support (unofficial)
552
- source_line_number+=1
553
- end
554
- end
555
- end
556
- source_line
557
- end
558
-
559
- private
560
-
561
- def __with_pd_options__(options=nil)
562
- options ||= {}
563
- permanent_options = PutsDebuggerer.options
564
- PutsDebuggerer.options = options.select {|option, _| PutsDebuggerer.options.keys.include?(option)}
565
- print_engine_options = options.delete_if {|option, _| PutsDebuggerer.options.keys.include?(option)}
566
- yield print_engine_options
567
- PutsDebuggerer.options = permanent_options
568
- end
569
-
570
- def __build_pd_data__(object, print_engine_options=nil)
571
- depth = PutsDebuggerer::CALLER_DEPTH_ZERO
572
- pd_data = {
573
- announcer: PutsDebuggerer.announcer,
574
- file: __caller_file__(depth).sub(PutsDebuggerer.app_path.to_s, ''),
575
- line_number: __caller_line_number__(depth),
576
- pd_expression: __caller_pd_expression__(depth),
577
- object: object,
578
- object_printer: lambda do
579
- if PutsDebuggerer.print_engine.is_a?(Proc)
580
- PutsDebuggerer.print_engine.call(object)
581
- else
582
- if print_engine_options.to_h.empty?
583
- send(PutsDebuggerer.print_engine, object)
584
- else
585
- send(PutsDebuggerer.print_engine, object, print_engine_options) rescue send(PutsDebuggerer.print_engine, object)
586
- end
587
- end
588
- end
589
- }
590
- if PutsDebuggerer.caller?
591
- start_depth = depth.to_i
592
- caller_depth = PutsDebuggerer.caller == -1 ? -1 : (start_depth + PutsDebuggerer.caller)
593
- pd_data[:caller] = caller[start_depth..caller_depth].to_a
594
- end
595
- pd_data[:header] = PutsDebuggerer.header if PutsDebuggerer.header?
596
- pd_data[:footer] = PutsDebuggerer.footer if PutsDebuggerer.footer?
597
- pd_data
598
- end
599
-
600
- def __format_pd_expression__(expression, object)
601
- "\n > #{expression}\n =>"
602
- end
603
-
604
- def __caller_pd_expression__(depth=0)
605
- # Caller Source Line Depth 2 = 1 to pd method + 1 to caller
606
- source_line = __caller_source_line__(depth+1)
607
- source_line = __extract_pd_expression__(source_line)
608
- source_line = source_line.gsub(/(^'|'$)/, '"') if source_line.start_with?("'") && source_line.end_with?("'")
609
- source_line = source_line.gsub(/(^\(|\)$)/, '') if source_line.start_with?("(") && source_line.end_with?(")")
610
- source_line
611
- end
612
-
613
- # Extracts pd source line expression.
614
- #
615
- # Example:
616
- #
617
- # __extract_pd_expression__("pd (x=1)")
618
- #
619
- # outputs `(x=1)`
620
- def __extract_pd_expression__(source_line)
621
- source_line.strip
622
- end
501
+ PutsDebuggerer.source_line_count = nil