tracer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32f50385faf52046ed48365c62f294a7d2d64fc99b5457c13e2bb7d239944883
4
- data.tar.gz: 14942209dd335bdde1267eaf6382b4ce1aad47200cfa6c3af82eadb8c03fae49
3
+ metadata.gz: f040d7e0181c8be3aa0d41e5be786aca6d3632c1367b6180a3cfc2b19bda57bf
4
+ data.tar.gz: 43143d936ab079338c0039718c021f6c9d50d8ac93ba5a1a90320eeed466eaf1
5
5
  SHA512:
6
- metadata.gz: 8969d4d076f10f25d0b1281d4decb8fd4c3b2aa39429e16a4771be45942b103450f7459afed93ad97bc44a73d50e9601805248729659bf9da36d50a279c0cfe7
7
- data.tar.gz: a83839fd50491fcfe4374acc42be6ab8000d3335431947d6d7a12c69a5fc1e0a5875fa2afb9ce00a41f7d24a66f69a7774195869083416f184282fa7dd3a34ea
6
+ metadata.gz: 51e73efd3fc7eea7b783f797f6afa67b1d7cee068383e36e076046a17462d1a7f36b58b85150bcbb700f0b7c24214b5893039241bfc16c27cc72f9fea4e6665b
7
+ data.tar.gz: 8cb8b671a2e630a0f03b1485f858320ccd7bcb260a06c41dbb373342774377c6911335d78e50f7f473b4722df24f070e8409c02c9ce3223455c001aaff08a8e8
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-02-07
4
+
5
+ - Initial release
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at stan.lo@shopify.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile CHANGED
@@ -1,6 +1,13 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in tracer.gemspec
5
+ # Specify your gem's dependencies in ruby-tracer.gemspec
6
6
  gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "irb"
10
+
11
+ gem "test-unit", "~> 3.0"
12
+
13
+ gem "ruby-lsp"
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ tracer (0.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ io-console (0.6.0)
10
+ irb (1.6.2)
11
+ reline (>= 0.3.0)
12
+ language_server-protocol (3.17.0.3)
13
+ power_assert (2.0.2)
14
+ prettier_print (1.2.0)
15
+ rake (13.0.6)
16
+ reline (0.3.2)
17
+ io-console (~> 0.5)
18
+ ruby-lsp (0.3.8)
19
+ language_server-protocol (~> 3.17.0)
20
+ sorbet-runtime
21
+ syntax_tree (>= 5.0.0, < 6)
22
+ sorbet-runtime (0.5.10648)
23
+ syntax_tree (5.3.0)
24
+ prettier_print (>= 1.2.0)
25
+ test-unit (3.5.5)
26
+ power_assert
27
+
28
+ PLATFORMS
29
+ arm64-darwin-21
30
+ arm64-darwin-22
31
+ x86_64-linux
32
+
33
+ DEPENDENCIES
34
+ irb
35
+ rake (~> 13.0)
36
+ ruby-lsp
37
+ test-unit (~> 3.0)
38
+ tracer!
39
+
40
+ BUNDLED WITH
41
+ 2.4.2
data/README.md CHANGED
@@ -1,84 +1,224 @@
1
1
  # Tracer
2
2
 
3
- Outputs a source level execution trace of a Ruby program.
4
-
5
- It does this by registering an event handler with Kernel#set_trace_func for processing incoming events. It also provides methods for filtering unwanted trace output (see Tracer.add_filter, Tracer.on, and Tracer.off).
3
+ Tracer is an extraction of [`ruby/debug`](https://github.com/ruby/debug)'s [powerful tracers](https://github.com/ruby/debug/blob/master/lib/debug/tracer.rb), with user-facing APIs, IRB-integration, and some improvements.
6
4
 
7
5
  ## Installation
8
6
 
9
- Add this line to your application's Gemfile:
7
+ ```shell
8
+ $ bundle add tracer --group=development,test
9
+ ```
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
10
12
 
11
- ```ruby
12
- gem 'tracer'
13
+ ```shell
14
+ $ gem install tracer
13
15
  ```
14
16
 
15
- And then execute:
17
+ ## Usage
16
18
 
17
- $ bundle
19
+ ```rb
20
+ Tracer.trace(object) { ... } # trace object's activities in the given block
21
+ Tracer.trace_call { ... } # trace method calls in the given block
22
+ Tracer.trace_exception { ... } # trace exceptions in the given block
23
+ ```
18
24
 
19
- Or install it yourself as:
25
+ **Example**
20
26
 
21
- $ gem install tracer
27
+ ```rb
28
+ require "tracer"
22
29
 
23
- ## Usage
30
+ obj = Object.new
31
+
32
+ def obj.foo
33
+ 100
34
+ end
35
+
36
+ def bar(obj)
37
+ obj.foo
38
+ end
39
+
40
+ Tracer.trace(obj) { bar(obj) }
41
+ #depth:1 #<Object:0x000000010903c190> is used as a parameter obj of Object#bar at test.rb:13:in `block in <main>'
42
+ #depth:2 #<Object:0x000000010903c190> receives .foo at test.rb:10:in `bar'
43
+ ```
44
+
45
+ ### `tracer/helper`
46
+
47
+ If you want to avoid the `Tracer` namespace, you can do `require "tracer/helper"` instead:
24
48
 
25
- Consider the following Ruby script
49
+ ```rb
50
+ require "tracer/helper"
26
51
 
52
+ trace(object) { ... } # trace object's activities in the given block
53
+ trace_call { ... } # trace method calls in the given block
54
+ trace_exception { ... } # trace exceptions in the given block
27
55
  ```
28
- class A
29
- def square(a)
30
- return a*a
31
- end
32
- end
33
-
34
- a = A.new
35
- a.square(5)
56
+
57
+ ### IRB-integration
58
+
59
+ Once required, `tracer` registers a few IRB commands to help you trace Ruby expressions:
60
+
61
+ ```
62
+ trace Trace the target object (or self) in the given expression. Usage: `trace [target,] <expression>`
63
+ trace_call Trace method calls in the given expression. Usage: `trace_call <expression>`
64
+ trace_exception Trace exceptions in the given expression. Usage: `trace_exception <expression>`
65
+ ```
66
+
67
+ **Example**
68
+
69
+ ```rb
70
+ # test.rb
71
+ require "tracer"
72
+
73
+ obj = Object.new
74
+
75
+ def obj.foo
76
+ 100
77
+ end
78
+
79
+ def bar(obj)
80
+ obj.foo
81
+ end
82
+
83
+ binding.irb
36
84
  ```
37
85
 
38
- Running the above script using <code>ruby -r tracer example.rb</code> will output the following trace to STDOUT (Note you can also explicitly <code>require 'tracer'</code>)
39
86
 
40
87
  ```
41
- #0:<internal:lib/rubygems/custom_require>:38:Kernel:<: -
42
- #0:example.rb:3::-: class A
43
- #0:example.rb:3::C: class A
44
- #0:example.rb:4::-: def square(a)
45
- #0:example.rb:7::E: end
46
- #0:example.rb:9::-: a = A.new
47
- #0:example.rb:10::-: a.square(5)
48
- #0:example.rb:4:A:>: def square(a)
49
- #0:example.rb:5:A:-: return a*a
50
- #0:example.rb:6:A:<: end
51
- | | | | |
52
- | | | | ---------------------+ event
53
- | | | ------------------------+ class
54
- | | --------------------------+ line
55
- | ------------------------------------+ filename
56
- ---------------------------------------+ thread
88
+ irb(main):001:0> trace obj, bar(obj)
89
+ #depth:23 #<Object:0x0000000107a86648> is used as a parameter obj of Object#bar at (eval):1:in `<main>'
90
+ #depth:24 #<Object:0x0000000107a86648> receives .foo at test.rb:10:in `bar'
91
+ => 100
92
+ irb(main):002:0> trace_call bar(obj)
93
+ #depth:23> Object#bar at (eval):1:in `<main>'
94
+ #depth:24> #<Object:0x0000000107a86648>.foo at test.rb:10:in `bar'
95
+ #depth:24< #<Object:0x0000000107a86648>.foo #=> 100 at test.rb:10:in `bar'
96
+ #depth:23< Object#bar #=> 100 at (eval):1:in `<main>'
97
+ => 100
98
+ ```
99
+
100
+ ### Tracer Classes
101
+
102
+ If you want to have more control over individual traces, you can use individual tracer classes:
103
+
104
+ #### ObjectTracer
105
+
106
+ ```rb
107
+ class User
108
+ def initialize(name) = (@name = name)
109
+
110
+ def name() = @name
111
+ end
112
+
113
+ def authorized?(user)
114
+ user.name == "John"
115
+ end
116
+
117
+ user = User.new("John")
118
+ tracer = ObjectTracer.new(user)
119
+ tracer.start do
120
+ user.name
121
+ authorized?(user)
122
+ end
123
+
124
+ #depth:3 #<User:0x000000010696cad8 @name="John"> receives #name (User#name) at test.rb:14:in `block in <main>'
125
+ #depth:3 #<User:0x000000010696cad8 @name="John"> is used as a parameter user of Object#authorized? at test.rb:15:in `block in <main>'
126
+ #depth:4 #<User:0x000000010696cad8 @name="John"> receives #name (User#name) at test.rb:8:in `authorized?'
57
127
  ```
58
128
 
59
- Symbol table used for displaying incoming events:
129
+ #### ExceptionTracer
130
+
131
+ ```rb
132
+ ExceptionTracer.new.start
133
+
134
+ begin
135
+ raise "boom"
136
+ rescue StandardError
137
+ nil
138
+ end
139
+
140
+ #depth:1 #<RuntimeError: boom> at test.rb:4
141
+ ```
60
142
 
143
+ #### CallTracer
144
+
145
+ ```rb
146
+ class User
147
+ def initialize(name) = (@name = name)
148
+
149
+ def name() = @name
150
+ end
151
+
152
+ def authorized?(user)
153
+ user.name == "John"
154
+ end
155
+
156
+ user = User.new("John")
157
+ tracer = CallTracer.new
158
+ tracer.start do
159
+ user.name
160
+ authorized?(user)
161
+ end
162
+
163
+ #depth:4 > block at test.rb:13
164
+ #depth:5 > User#name at test.rb:4
165
+ #depth:5 < User#name #=> "John" at test.rb:4
166
+ #depth:5 > Object#authorized? at test.rb:7
167
+ #depth:6 > User#name at test.rb:4
168
+ #depth:6 < User#name #=> "John" at test.rb:4
169
+ #depth:6 > String#== at test.rb:8
170
+ #depth:6 < String#== #=> true at test.rb:8
171
+ #depth:5 < Object#authorized? #=> true at test.rb:9
172
+ #depth:4 < block #=> true at test.rb:16
61
173
  ```
62
- +}+:: call a C-language routine
63
- +{+:: return from a C-language routine
64
- +>+:: call a Ruby method
65
- +C+:: start a class or module definition
66
- +E+:: finish a class or module definition
67
- +-+:: execute code on a new line
68
- +^+:: raise an exception
69
- +<+:: return from a Ruby method
174
+
175
+ #### LineTracer
176
+
177
+ ```rb
178
+ class User
179
+ def initialize(name) = (@name = name)
180
+
181
+ def name() = @name
182
+ end
183
+
184
+ def authorized?(user)
185
+ user.name == "John"
186
+ end
187
+
188
+ user = User.new("John")
189
+ tracer = LineTracer.new
190
+ tracer.start do
191
+ user.name
192
+ authorized?(user)
193
+ end
194
+
195
+ #depth:4 at test.rb:14
196
+ #depth:4 at test.rb:15
197
+ #depth:5 at test.rb:8
70
198
  ```
71
199
 
200
+ ## Customization
201
+
202
+ TBD
203
+
204
+ ## Acknowledgement
205
+
206
+ [@ko1](https://github.com/ko1) (Koichi Sasada) implemented the majority of [`ruby/debug`](https://github.com/ruby/debug), including its tracers. So this project wouldn't exist without his amazing work there.
207
+
72
208
  ## Development
73
209
 
74
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
210
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test-unit` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
75
211
 
76
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
212
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
77
213
 
78
214
  ## Contributing
79
215
 
80
- Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/tracer.
216
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/tracer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ruby/tracer/blob/master/CODE_OF_CONDUCT.md).
81
217
 
82
218
  ## License
83
219
 
84
220
  The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause).
221
+
222
+ ## Code of Conduct
223
+
224
+ Everyone interacting in the Ruby::Tracer project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ruby/tracer/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rake/testtask"
3
5
 
4
6
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test" << "test/lib"
7
+ t.libs << "test"
6
8
  t.libs << "lib"
7
- t.test_files = FileList["test/**/test_*.rb"]
9
+ t.test_files = FileList["test/**/*_test.rb"]
8
10
  end
9
11
 
10
- task :default => :test
12
+ task default: :test
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pp"
4
+ require_relative "color"
5
+
6
+ module Tracer
7
+ class Base
8
+ DIR = __dir__
9
+ M_OBJECT_ID = Object.instance_method(:object_id)
10
+ M_INSPECT = Object.instance_method(:inspect)
11
+ M_CLASS = Object.instance_method(:class)
12
+ M_IS_A = Object.instance_method(:is_a?)
13
+ HOME = ENV["HOME"] ? (ENV["HOME"] + "/") : nil
14
+
15
+ include Color
16
+
17
+ class LimitedPP
18
+ def self.pp(obj, max)
19
+ out = self.new(max)
20
+ catch out do
21
+ PP.singleline_pp(obj, out)
22
+ end
23
+ out.buf
24
+ end
25
+
26
+ attr_reader :buf
27
+
28
+ def initialize(max)
29
+ @max = max
30
+ @cnt = 0
31
+ @buf = String.new
32
+ end
33
+
34
+ def <<(other)
35
+ @buf << other
36
+
37
+ if @buf.size >= @max
38
+ @buf = @buf[0..@max] + "..."
39
+ throw self
40
+ end
41
+ end
42
+ end
43
+
44
+ def safe_inspect(obj, max_length: 40)
45
+ LimitedPP.pp(obj, max_length)
46
+ rescue NoMethodError => e
47
+ klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
48
+ if obj == (r = e.receiver)
49
+ "#<#{klass.name}#{oid} does not have \#inspect>"
50
+ else
51
+ rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
52
+ "#<#{klass.name}:#{roid} contains #<#{rklass}:#{roid} and it does not have #inspect>"
53
+ end
54
+ rescue Exception => e
55
+ "<#inspect raises #{e.inspect}>"
56
+ end
57
+
58
+ def pretty_path(path)
59
+ return "#<none>" unless path
60
+
61
+ case
62
+ when path.start_with?(dir = RbConfig::CONFIG["rubylibdir"] + "/")
63
+ path.sub(dir, "$(rubylibdir)/")
64
+ when Gem.path.any? { |gp| path.start_with?(dir = gp + "/gems/") }
65
+ path.sub(dir, "$(Gem)/")
66
+ when HOME && path.start_with?(HOME)
67
+ path.sub(HOME, "~/")
68
+ else
69
+ path
70
+ end
71
+ end
72
+
73
+ def initialize(output: STDOUT, pattern: nil, colorize: nil, depth_offset: 0)
74
+ @name = self.class.name
75
+ @type = @name.sub(/Tracer\z/, "")
76
+ @output = output
77
+ @depth_offset = depth_offset
78
+ @colorize = colorize || colorizable?
79
+
80
+ if pattern
81
+ @pattern = Regexp.compile(pattern)
82
+ else
83
+ @pattern = nil
84
+ end
85
+
86
+ @tp = setup_tp
87
+ end
88
+
89
+ def key
90
+ [@type, @pattern, @into].freeze
91
+ end
92
+
93
+ def header
94
+ ""
95
+ end
96
+
97
+ def to_s
98
+ s = "#{@name} #{description}"
99
+ s += " with pattern #{@pattern.inspect}" if @pattern
100
+ s
101
+ end
102
+
103
+ def description
104
+ "(#{@tp.enabled? ? "enabled" : "disabled"})"
105
+ end
106
+
107
+ def start(&block)
108
+ puts "PID:#{Process.pid} #{self}" if @output.is_a?(File)
109
+
110
+ if block
111
+ @tp.enable(&block)
112
+ else
113
+ @tp.enable
114
+ self
115
+ end
116
+ end
117
+
118
+ def stop
119
+ @tp.disable
120
+ end
121
+
122
+ def started?
123
+ @tp.enabled?
124
+ end
125
+
126
+ def stopped?
127
+ !started?
128
+ end
129
+
130
+ def skip?(tp)
131
+ skip_internal?(tp) || skip_with_pattern?(tp)
132
+ end
133
+
134
+ def skip_with_pattern?(tp)
135
+ @pattern && !tp.path.match?(@pattern)
136
+ end
137
+
138
+ def skip_internal?(tp)
139
+ tp.path.match?(DIR)
140
+ end
141
+
142
+ def out(tp, msg = nil, depth: caller.size - 1, location: nil)
143
+ location ||= "#{tp.path}:#{tp.lineno}"
144
+ buff =
145
+ "#{header} \#depth:#{"%-2d" % depth}#{msg} at #{colorize("#{location}", [:GREEN])}"
146
+
147
+ puts buff
148
+ end
149
+
150
+ def puts(msg)
151
+ @output.puts msg
152
+ @output.flush
153
+ end
154
+
155
+ def minfo(tp)
156
+ return "block{}" if tp.event == :b_call
157
+
158
+ klass = tp.defined_class
159
+
160
+ if klass.singleton_class?
161
+ "#{tp.self}.#{tp.method_id}"
162
+ else
163
+ "#{klass}\##{tp.method_id}"
164
+ end
165
+ end
166
+
167
+ def colorizable?
168
+ no_color = (nc = ENV["NO_COLOR"]).nil? || nc.empty?
169
+ @output.is_a?(IO) && @output.tty? && no_color
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class CallTracer < Tracer::Base
6
+ def setup_tp
7
+ TracePoint.new(:a_call, :a_return) do |tp|
8
+ next if skip?(tp)
9
+
10
+ location = caller_locations(2, 1).first.to_s
11
+ next if location.match?(DIR) || location.match?(/<internal:/)
12
+
13
+ depth = caller.size
14
+
15
+ call_identifier_str = (tp.defined_class ? minfo(tp) : "block")
16
+ call_identifier_str = colorize_blue(call_identifier_str)
17
+
18
+ case tp.event
19
+ when :call, :c_call, :b_call
20
+ depth += 1 if tp.event == :c_call
21
+ sp = " " * depth
22
+ out tp,
23
+ ">#{sp}#{call_identifier_str}",
24
+ depth: depth - 2 - @depth_offset,
25
+ location: location
26
+ when :return, :c_return, :b_return
27
+ depth += 1 if tp.event == :c_return
28
+ sp = " " * depth
29
+ return_str = colorize_magenta(safe_inspect(tp.return_value))
30
+ out tp,
31
+ "<#{sp}#{call_identifier_str} #=> #{return_str}",
32
+ depth: depth - 2 - @depth_offset,
33
+ location: location
34
+ end
35
+ end
36
+ end
37
+
38
+ def skip_with_pattern?(tp)
39
+ super && !tp.method_id&.match?(@pattern)
40
+ end
41
+ end