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.
@@ -0,0 +1,224 @@
1
+ require 'stringio'
2
+
3
+ module Kernel
4
+ # Prints object with bonus info such as file name, line number and source
5
+ # expression. Optionally prints out header and footer.
6
+ # Lookup PutsDebuggerer attributes for more details about configuration options.
7
+ #
8
+ # Simply invoke global `pd` method anywhere you'd like to see line number and source code with output.
9
+ # If the argument is a pure string, the print out is simplified by not showing duplicate source.
10
+ #
11
+ # Quickly locate printed lines using Find feature (e.g. CTRL+F) by looking for:
12
+ # * \[PD\]
13
+ # * file:line_number
14
+ # * ruby expression.
15
+ #
16
+ # This gives you the added benefit of easily removing your pd statements later on from the code.
17
+ #
18
+ # Happy puts_debuggerering!
19
+ #
20
+ # Example Code:
21
+ #
22
+ # # /Users/User/finance_calculator_app/pd_test.rb # line 1
23
+ # bug = 'beattle' # line 2
24
+ # pd "Show me the source of the bug: #{bug}" # line 3
25
+ # pd 'What line number am I?' # line 4
26
+ #
27
+ # Example Printout:
28
+ #
29
+ # [PD] /Users/User/finance_calculator_app/pd_test.rb:3
30
+ # > pd "Show me the source of the bug: #{bug}"
31
+ # => "Show me the source of the bug: beattle"
32
+ # [PD] /Users/User/finance_calculator_app/pd_test.rb:4 "What line number am I?"
33
+ def pd(*objects)
34
+ options = PutsDebuggerer.determine_options(objects) || {}
35
+ object = PutsDebuggerer.determine_object(objects)
36
+ run_at = PutsDebuggerer.determine_run_at(options)
37
+ printer = PutsDebuggerer.determine_printer(options)
38
+ pd_inspect = options.delete(:pd_inspect)
39
+ logger_formatter_decorated = PutsDebuggerer.printer.is_a?(Logger) && PutsDebuggerer.printer.formatter != PutsDebuggerer.logger_original_formatter
40
+ logging_layouts_decorated = PutsDebuggerer.printer.is_a?(Logging::Logger) && PutsDebuggerer.printer.appenders.map(&:layout) != (PutsDebuggerer.logging_original_layouts.values)
41
+
42
+ string = nil
43
+ if PutsDebuggerer::RunDeterminer.run_pd?(object, run_at)
44
+ __with_pd_options__(options) do |print_engine_options|
45
+ run_number = PutsDebuggerer::RunDeterminer.run_number(object, run_at)
46
+ formatter_pd_data = __build_pd_data__(object, print_engine_options: print_engine_options, source_line_count: PutsDebuggerer.source_line_count, run_number: run_number, pd_inspect: pd_inspect, logger_formatter_decorated: logger_formatter_decorated, logging_layouts_decorated: logging_layouts_decorated)
47
+ stdout = $stdout
48
+ $stdout = sio = StringIO.new
49
+ PutsDebuggerer.formatter.call(formatter_pd_data)
50
+ $stdout = stdout
51
+ string = sio.string
52
+ if RUBY_ENGINE == 'opal' && object.is_a?(Exception)
53
+ $stderr.puts(string)
54
+ else
55
+ if PutsDebuggerer.printer.is_a?(Proc)
56
+ PutsDebuggerer.printer.call(string)
57
+ elsif PutsDebuggerer.printer.is_a?(Logger)
58
+ logger_formatter = PutsDebuggerer.printer.formatter
59
+ begin
60
+ PutsDebuggerer.printer.formatter = PutsDebuggerer.logger_original_formatter
61
+ PutsDebuggerer.printer.debug(string)
62
+ ensure
63
+ PutsDebuggerer.printer.formatter = logger_formatter
64
+ end
65
+ elsif PutsDebuggerer.printer.is_a?(Logging::Logger)
66
+ logging_layouts = PutsDebuggerer.printer.appenders.reduce({}) do |hash, appender|
67
+ hash.merge(appender => appender.layout)
68
+ end
69
+ begin
70
+ PutsDebuggerer.logging_original_layouts.each do |appender, original_layout|
71
+ appender.layout = original_layout
72
+ end
73
+ PutsDebuggerer.printer.debug(string)
74
+ ensure
75
+ PutsDebuggerer.logging_original_layouts.each do |appender, original_layout|
76
+ appender.layout = logging_layouts[appender]
77
+ end
78
+ end
79
+ elsif PutsDebuggerer.printer != false
80
+ send(PutsDebuggerer.send(:printer), string)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ printer ? object : string
87
+ end
88
+
89
+ # Implement caller backtrace method in Opal since it returns an empty array in Opal v1
90
+ if RUBY_ENGINE == 'opal'
91
+ def caller
92
+ begin
93
+ raise 'error'
94
+ rescue => e
95
+ e.backtrace[2..-1]
96
+ end
97
+ end
98
+ end
99
+
100
+ def pd_inspect
101
+ pd self, printer: false, pd_inspect: true
102
+ end
103
+ alias pdi pd_inspect
104
+
105
+ # Provides caller line number starting 1 level above caller of
106
+ # this method.
107
+ #
108
+ # Example:
109
+ #
110
+ # # lib/example.rb # line 1
111
+ # puts "Print out __caller_line_number__" # line 2
112
+ # puts __caller_line_number__ # line 3
113
+ #
114
+ # prints out `3`
115
+ def __caller_line_number__(caller_depth=0)
116
+ return if RUBY_ENGINE == 'opal'
117
+ caller[caller_depth] && caller[caller_depth][PutsDebuggerer::STACK_TRACE_CALL_LINE_NUMBER_REGEX, 1].to_i
118
+ end
119
+
120
+ # Provides caller file starting 1 level above caller of
121
+ # this method.
122
+ #
123
+ # Example:
124
+ #
125
+ # # File Name: lib/example.rb
126
+ # puts __caller_file__
127
+ #
128
+ # prints out `lib/example.rb`
129
+ def __caller_file__(caller_depth=0)
130
+ regex = RUBY_ENGINE == 'opal' ? PutsDebuggerer::STACK_TRACE_CALL_SOURCE_FILE_REGEX_OPAL : PutsDebuggerer::STACK_TRACE_CALL_SOURCE_FILE_REGEX
131
+ caller[caller_depth] && caller[caller_depth][regex, 1]
132
+ end
133
+
134
+
135
+ # Provides caller source line starting 1 level above caller of
136
+ # this method.
137
+ #
138
+ # Example:
139
+ #
140
+ # puts __caller_source_line__
141
+ #
142
+ # prints out `puts __caller_source_line__`
143
+ def __caller_source_line__(caller_depth=0, source_line_count=nil, source_file=nil, source_line_number=nil)
144
+ source_line_number ||= __caller_line_number__(caller_depth+1)
145
+ source_file ||= __caller_file__(caller_depth+1)
146
+ source_line = ''
147
+ if defined?(Pry)
148
+ @pry_instance ||= Pry.new
149
+ source_line = Pry::Command::Hist.new(pry_instance: @pry_instance).call.instance_variable_get(:@buffer).split("\n")[source_line_number - 1] # TODO handle multi-lines in source_line_count
150
+ elsif defined?(IRB)
151
+ source_line = TOPLEVEL_BINDING.receiver.conf.io.line(source_line_number) # TODO handle multi-lines in source_line_count
152
+ else
153
+ source_line = PutsDebuggerer::SourceFile.new(source_file).source(source_line_count, source_line_number)
154
+ end
155
+ source_line
156
+ end
157
+
158
+ private
159
+
160
+ def __with_pd_options__(options=nil)
161
+ options ||= {}
162
+ permanent_options = PutsDebuggerer.options
163
+ PutsDebuggerer.options = options.select {|option, _| PutsDebuggerer.options.keys.include?(option)}
164
+ print_engine_options = options.delete_if {|option, _| PutsDebuggerer.options.keys.include?(option)}
165
+ yield print_engine_options
166
+ PutsDebuggerer.options = permanent_options
167
+ end
168
+
169
+ def __build_pd_data__(object, print_engine_options:nil, source_line_count:nil, run_number:nil, pd_inspect:false, logger_formatter_decorated:false, logging_layouts_decorated:false)
170
+ depth = RUBY_ENGINE == 'opal' ? PutsDebuggerer::CALLER_DEPTH_ZERO_OPAL : PutsDebuggerer::CALLER_DEPTH_ZERO
171
+ if pd_inspect
172
+ depth += 1
173
+ depth += 4 if logger_formatter_decorated
174
+ depth += 8 if logging_layouts_decorated
175
+ end
176
+
177
+ pd_data = {
178
+ announcer: PutsDebuggerer.announcer,
179
+ file: __caller_file__(depth)&.sub(PutsDebuggerer.app_path.to_s, ''),
180
+ line_number: __caller_line_number__(depth),
181
+ pd_expression: __caller_pd_expression__(depth, source_line_count),
182
+ run_number: run_number,
183
+ object: object,
184
+ object_printer: PutsDebuggerer::OBJECT_PRINTER_DEFAULT.call(object, print_engine_options, source_line_count, run_number)
185
+ }
186
+ pd_data[:caller] = __caller_caller__(depth)
187
+ ['header', 'wrapper', 'footer'].each do |boundary_option|
188
+ pd_data[boundary_option.to_sym] = PutsDebuggerer.send(boundary_option) if PutsDebuggerer.send("#{boundary_option}?")
189
+ end
190
+ pd_data
191
+ end
192
+
193
+ # Returns the caller stack trace of the caller of pd
194
+ def __caller_caller__(depth)
195
+ return unless PutsDebuggerer.caller?
196
+ start_depth = depth.to_i + 1
197
+ caller_depth = PutsDebuggerer.caller == -1 ? -1 : (start_depth + PutsDebuggerer.caller)
198
+ caller[start_depth..caller_depth].to_a
199
+ end
200
+
201
+ def __format_pd_expression__(expression, object)
202
+ "\n > #{expression}\n =>"
203
+ end
204
+
205
+ def __caller_pd_expression__(depth=0, source_line_count=nil)
206
+ # Caller Source Line Depth 2 = 1 to pd method + 1 to caller
207
+ source_line = __caller_source_line__(depth+1, source_line_count)
208
+ source_line = __extract_pd_expression__(source_line)
209
+ source_line = source_line.gsub(/(^'|'$)/, '"') if source_line.start_with?("'") && source_line.end_with?("'")
210
+ source_line = source_line.gsub(/(^\(|\)$)/, '') if source_line.start_with?("(") && source_line.end_with?(")")
211
+ source_line
212
+ end
213
+
214
+ # Extracts pd source line expression.
215
+ #
216
+ # Example:
217
+ #
218
+ # __extract_pd_expression__("pd (x=1)")
219
+ #
220
+ # outputs `(x=1)`
221
+ def __extract_pd_expression__(source_line)
222
+ source_line.to_s.strip
223
+ end
224
+ end
@@ -0,0 +1,3 @@
1
+ # in case 'logger' is not required
2
+ class Logger
3
+ end
@@ -0,0 +1,5 @@
1
+ # in case 'logging' is not required
2
+ module Logging
3
+ class Logger
4
+ end
5
+ end
@@ -0,0 +1,109 @@
1
+ module PutsDebuggerer
2
+ module RunDeterminer
3
+ OBJECT_RUN_AT = {}
4
+
5
+ class << self
6
+ attr_reader :run_at_global_number
7
+
8
+ def run_at_global_number=(value)
9
+ @run_at_global_number = value
10
+ end
11
+
12
+ def init_run_at_global_number
13
+ @run_at_global_number = 1
14
+ end
15
+
16
+ def increment_run_at_global_number
17
+ @run_at_global_number += 1
18
+ end
19
+
20
+ def reset_run_at_global_number
21
+ @run_at_global_number = nil
22
+ end
23
+
24
+ def run_at_number(object, run_at)
25
+ OBJECT_RUN_AT[[object,run_at]]
26
+ end
27
+
28
+ def init_run_at_number(object, run_at)
29
+ OBJECT_RUN_AT[[object,run_at]] = 1
30
+ end
31
+
32
+ def increment_run_at_number(object, run_at)
33
+ OBJECT_RUN_AT[[object,run_at]] += 1
34
+ end
35
+
36
+ def reset_run_at_number(object, run_at)
37
+ OBJECT_RUN_AT.delete([object, run_at])
38
+ end
39
+
40
+ def reset_run_at_numbers
41
+ OBJECT_RUN_AT.clear
42
+ end
43
+
44
+ def run_number(object, run_at)
45
+ run_at_global_number || run_at_number(object, run_at)
46
+ end
47
+
48
+ def run_pd?(object, run_at)
49
+ run_pd = false
50
+ if run_at.nil?
51
+ run_pd = true
52
+ else
53
+ run_number = determine_run_number(object, run_at)
54
+ run_pd = determine_run_pd(run_at, run_number)
55
+ end
56
+ run_pd
57
+ end
58
+
59
+ def determine_run_number(object, run_at)
60
+ if PutsDebuggerer.run_at? # check if global option is set
61
+ determine_global_run_number
62
+ else
63
+ determine_local_run_number(object, run_at)
64
+ end
65
+ end
66
+
67
+ def determine_global_run_number
68
+ if PutsDebuggerer::RunDeterminer.run_at_global_number.nil?
69
+ PutsDebuggerer::RunDeterminer.init_run_at_global_number
70
+ else
71
+ PutsDebuggerer::RunDeterminer.increment_run_at_global_number
72
+ end
73
+ PutsDebuggerer::RunDeterminer.run_at_global_number
74
+ end
75
+
76
+ def determine_local_run_number(object, run_at)
77
+ if PutsDebuggerer::RunDeterminer.run_at_number(object, run_at).nil?
78
+ PutsDebuggerer::RunDeterminer.init_run_at_number(object, run_at)
79
+ else
80
+ PutsDebuggerer::RunDeterminer.increment_run_at_number(object, run_at)
81
+ end
82
+ PutsDebuggerer::RunDeterminer.run_at_number(object, run_at)
83
+ end
84
+
85
+ def determine_run_pd(run_at, run_number)
86
+ if run_at.is_a?(Integer)
87
+ determine_run_pd_integer(run_at, run_number)
88
+ elsif run_at.is_a?(Array)
89
+ determine_run_pd_array(run_at, run_number)
90
+ elsif run_at.is_a?(Range)
91
+ determine_run_pd_range(run_at, run_number)
92
+ end
93
+ end
94
+
95
+ def determine_run_pd_integer(run_at, run_number)
96
+ run_pd = true if run_at == run_number
97
+ end
98
+
99
+ def determine_run_pd_array(run_at, run_number)
100
+ run_pd = true if run_at.include?(run_number)
101
+ end
102
+
103
+ def determine_run_pd_range(run_at, run_number)
104
+ run_pd = true if run_at.cover?(run_number) || (run_at.end == -1 && run_number >= run_at.begin)
105
+ end
106
+
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,32 @@
1
+ module PutsDebuggerer
2
+ class SourceFile
3
+ def initialize(file_path)
4
+ @file = File.new(file_path) if file_path
5
+ end
6
+
7
+ def source(source_line_count, source_line_number)
8
+ @source = ''
9
+ return @source if RUBY_ENGINE == 'opal'
10
+ # For Opal Ruby compatibility, skip source lines if file does not respond to readline (as in Opal)
11
+ lines = source_lines(source_line_count, source_line_number)
12
+ @source = lines.join(' '*5) if @file.respond_to?(:readline)
13
+ @source
14
+ end
15
+
16
+ def source_lines(source_line_count, source_line_number)
17
+ lines = []
18
+ begin
19
+ while @file.lineno < source_line_number + source_line_count
20
+ file_line_number = @file.lineno + 1
21
+ file_line = @file.readline
22
+ if file_line_number >= source_line_number && file_line_number < source_line_number + source_line_count
23
+ lines << file_line
24
+ end
25
+ end
26
+ rescue EOFError
27
+ # Done
28
+ end
29
+ lines
30
+ end
31
+ end
32
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puts_debuggerer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-24 00:00:00.000000000 Z
11
+ date: 2021-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_print
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.8.0
19
+ version: 1.9.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.8.0
26
+ version: 1.9.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -84,14 +84,14 @@ dependencies:
84
84
  name: bundler
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: 2.1.4
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: 2.1.4
97
97
  - !ruby/object:Gem::Dependency
@@ -137,19 +137,33 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: 0.7.0
139
139
  - !ruby/object:Gem::Dependency
140
- name: undercover
140
+ name: logging
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - "~>"
143
+ - - ">="
144
144
  - !ruby/object:Gem::Version
145
- version: 0.3.4
145
+ version: 2.3.0
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - "~>"
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 2.3.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: rake-tui
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
151
165
  - !ruby/object:Gem::Version
152
- version: 0.3.4
166
+ version: '0'
153
167
  description: |
154
168
  Debuggers are great! They help us troubleshoot complicated programming problems by inspecting values produced by code, line by line. They are invaluable when trying to understand what is going on in a large application composed of thousands or millions of lines of code.
155
169
  In day-to-day test-driven development and simple debugging though, a puts statement can be a lot quicker in revealing what is going on than halting execution completely just to inspect a single value or a few. This is certainly true when writing the simplest possible code that could possibly work, and running a test every few seconds or minutes. Problem is you need to locate puts statements in large output logs, know which methods were invoked, find out what variable names are being printed, and see nicely formatted output. Enter puts_debuggerer. A guilt-free puts debugging Ruby gem FTW that prints file names, line numbers, code statements, and formats output nicely courtesy of awesome_print.
@@ -166,7 +180,13 @@ files:
166
180
  - LICENSE.txt
167
181
  - README.md
168
182
  - VERSION
183
+ - lib/pd.rb
169
184
  - lib/puts_debuggerer.rb
185
+ - lib/puts_debuggerer/core_ext/kernel.rb
186
+ - lib/puts_debuggerer/core_ext/logger.rb
187
+ - lib/puts_debuggerer/core_ext/logging/logger.rb
188
+ - lib/puts_debuggerer/run_determiner.rb
189
+ - lib/puts_debuggerer/source_file.rb
170
190
  homepage: http://github.com/AndyObtiva/puts_debuggerer
171
191
  licenses:
172
192
  - MIT
@@ -186,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
206
  - !ruby/object:Gem::Version
187
207
  version: '0'
188
208
  requirements: []
189
- rubygems_version: 3.1.2
209
+ rubygems_version: 3.2.3
190
210
  signing_key:
191
211
  specification_version: 4
192
212
  summary: Ruby library for improved puts debugging, automatically displaying bonus