tapping_device 0.5.2 → 0.5.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbf652959b6f125f5e4ece39c59db0b8ebd55096e4aa639452de306857193bf7
4
- data.tar.gz: 31d13f1f4f38b6009795e4d948ba4596428355cb03d113603b3801c307c14a18
3
+ metadata.gz: 9311ddb749d3c7bf09306161da25191c0de47734dea77f645f5d5af62a55d802
4
+ data.tar.gz: 4df7c9fef5c3a3a2b3852b5b4f4db31ec071149dd19b3fceb1df6f6b1ac191e8
5
5
  SHA512:
6
- metadata.gz: 98eae30679e7e08f7607dbd7ee457b9db9565e46ddf2859a782f2b5ce817bf014937b65f8ee0f342bb7d6dbec3afcb3aef980e396362726839c4031db03cb2e5
7
- data.tar.gz: bf2d0e6e8e38466f928d57580edf745b1ee1ce18ed1ee03968dc76647b67cfb4e0feaeff0166ac7503c295c54351445344f828a0bc3878b57a74ed8a130a03cb
6
+ metadata.gz: deaece3e6096551df6fe3c7eb492f6049e72b32f7e19b7a8d4067f8d5a6dc9b6fe56a41d3bd9855220ae11c8adcc377cd188f51c043835845be5c2d9250bc6d1
7
+ data.tar.gz: 057766f277605fb3e80bd2797ee1256f0f6d81fbca56c95226199a72c7e14565dc353292d06711847e257d2f552e9eb3ddcd076ec35f1abd3aaf30cc9f686f5c
data/.DS_Store CHANGED
Binary file
@@ -0,0 +1,208 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased](https://github.com/st0012/tapping_device/tree/HEAD)
4
+
5
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.2...HEAD)
6
+
7
+ **Closed issues:**
8
+
9
+ - Support write\_\* helpers [\#44](https://github.com/st0012/tapping_device/issues/44)
10
+ - Use Method\#source to replace Payload\#method\_head’s implementation [\#19](https://github.com/st0012/tapping_device/issues/19)
11
+
12
+ **Merged pull requests:**
13
+
14
+ - Support write\_\* helpers [\#47](https://github.com/st0012/tapping_device/pull/47) ([st0012](https://github.com/st0012))
15
+ - Hijack attr methods with `hijack\_attr\_methods` option [\#45](https://github.com/st0012/tapping_device/pull/45) ([st0012](https://github.com/st0012))
16
+
17
+ ## [v0.5.2](https://github.com/st0012/tapping_device/tree/v0.5.2) (2020-06-10)
18
+
19
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.1...v0.5.2)
20
+
21
+ **Closed issues:**
22
+
23
+ - Add print\_mutations [\#41](https://github.com/st0012/tapping_device/issues/41)
24
+ - Add tap\_on\_mutation! [\#18](https://github.com/st0012/tapping_device/issues/18)
25
+
26
+ **Merged pull requests:**
27
+
28
+ - Print mutations [\#43](https://github.com/st0012/tapping_device/pull/43) ([st0012](https://github.com/st0012))
29
+ - Refactorings [\#42](https://github.com/st0012/tapping_device/pull/42) ([st0012](https://github.com/st0012))
30
+
31
+ ## [v0.5.1](https://github.com/st0012/tapping_device/tree/v0.5.1) (2020-06-07)
32
+
33
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.0...v0.5.1)
34
+
35
+ **Fixed bugs:**
36
+
37
+ - Filter Out Entries From TappingDevice [\#35](https://github.com/st0012/tapping_device/issues/35)
38
+
39
+ **Merged pull requests:**
40
+
41
+ - Update GitHub Actions Configuration [\#40](https://github.com/st0012/tapping_device/pull/40) ([st0012](https://github.com/st0012))
42
+ - Fix typo: Guadian -\> Guardian [\#39](https://github.com/st0012/tapping_device/pull/39) ([skade](https://github.com/skade))
43
+ - Filter out calls about TappingDevice [\#38](https://github.com/st0012/tapping_device/pull/38) ([st0012](https://github.com/st0012))
44
+ - Drop tap\_sql! [\#37](https://github.com/st0012/tapping_device/pull/37) ([st0012](https://github.com/st0012))
45
+ - Add CollectionProxy class [\#36](https://github.com/st0012/tapping_device/pull/36) ([st0012](https://github.com/st0012))
46
+
47
+ ## [v0.5.0](https://github.com/st0012/tapping_device/tree/v0.5.0) (2020-05-25)
48
+
49
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.11...v0.5.0)
50
+
51
+ **Closed issues:**
52
+
53
+ - Colorize output of tracing helpers [\#25](https://github.com/st0012/tapping_device/issues/25)
54
+
55
+ **Merged pull requests:**
56
+
57
+ - Update README.md [\#34](https://github.com/st0012/tapping_device/pull/34) ([st0012](https://github.com/st0012))
58
+ - Colorize output [\#33](https://github.com/st0012/tapping_device/pull/33) ([st0012](https://github.com/st0012))
59
+ - Add TappingDevice\#with to register a with condition [\#32](https://github.com/st0012/tapping_device/pull/32) ([st0012](https://github.com/st0012))
60
+
61
+ ## [v0.4.11](https://github.com/st0012/tapping_device/tree/v0.4.11) (2020-04-19)
62
+
63
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.10...v0.4.11)
64
+
65
+ **Merged pull requests:**
66
+
67
+ - Update rake requirement from ~\> 10.0 to ~\> 13.0 [\#31](https://github.com/st0012/tapping_device/pull/31) ([dependabot[bot]](https://github.com/apps/dependabot))
68
+
69
+ ## [v0.4.10](https://github.com/st0012/tapping_device/tree/v0.4.10) (2020-02-05)
70
+
71
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.9...v0.4.10)
72
+
73
+ **Implemented enhancements:**
74
+
75
+ - Usability improvements [\#30](https://github.com/st0012/tapping_device/pull/30) ([st0012](https://github.com/st0012))
76
+
77
+ **Merged pull requests:**
78
+
79
+ - Fix tap\_init!'s payload content [\#29](https://github.com/st0012/tapping_device/pull/29) ([st0012](https://github.com/st0012))
80
+ - Refactorings and fixes [\#28](https://github.com/st0012/tapping_device/pull/28) ([st0012](https://github.com/st0012))
81
+
82
+ ## [v0.4.9](https://github.com/st0012/tapping_device/tree/v0.4.9) (2020-01-20)
83
+
84
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.8...v0.4.9)
85
+
86
+ **Implemented enhancements:**
87
+
88
+ - Improve detail\_call\_info's output format [\#27](https://github.com/st0012/tapping_device/pull/27) ([st0012](https://github.com/st0012))
89
+
90
+ ## [v0.4.8](https://github.com/st0012/tapping_device/tree/v0.4.8) (2020-01-05)
91
+
92
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.7...v0.4.8)
93
+
94
+ **Closed issues:**
95
+
96
+ - Provide options for tapping on call or return events [\#23](https://github.com/st0012/tapping_device/issues/23)
97
+
98
+ **Merged pull requests:**
99
+
100
+ - Add tracing helpers [\#24](https://github.com/st0012/tapping_device/pull/24) ([st0012](https://github.com/st0012))
101
+
102
+ ## [v0.4.7](https://github.com/st0012/tapping_device/tree/v0.4.7) (2019-12-29)
103
+
104
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.6...v0.4.7)
105
+
106
+ **Implemented enhancements:**
107
+
108
+ - Config test coverage for codeclimate [\#22](https://github.com/st0012/tapping_device/pull/22) ([st0012](https://github.com/st0012))
109
+
110
+ **Closed issues:**
111
+
112
+ - Support tracking ActiveRecord::Base instances by their ids [\#17](https://github.com/st0012/tapping_device/issues/17)
113
+
114
+ **Merged pull requests:**
115
+
116
+ - Refactor tests and some minor fixes [\#21](https://github.com/st0012/tapping_device/pull/21) ([st0012](https://github.com/st0012))
117
+ - Support track\_as\_records option [\#20](https://github.com/st0012/tapping_device/pull/20) ([st0012](https://github.com/st0012))
118
+
119
+ ## [v0.4.6](https://github.com/st0012/tapping_device/tree/v0.4.6) (2019-12-25)
120
+
121
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.5...v0.4.6)
122
+
123
+ **Merged pull requests:**
124
+
125
+ - Add TappingDevice\#and\_print method [\#16](https://github.com/st0012/tapping_device/pull/16) ([st0012](https://github.com/st0012))
126
+ - Add Payload\#detail\_call\_info and improve method\_name's format [\#15](https://github.com/st0012/tapping_device/pull/15) ([st0012](https://github.com/st0012))
127
+
128
+ ## [v0.4.5](https://github.com/st0012/tapping_device/tree/v0.4.5) (2019-12-15)
129
+
130
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.4...v0.4.5)
131
+
132
+ ## [v0.4.4](https://github.com/st0012/tapping_device/tree/v0.4.4) (2019-12-15)
133
+
134
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.3...v0.4.4)
135
+
136
+ **Merged pull requests:**
137
+
138
+ - Implement tap\_passed! [\#14](https://github.com/st0012/tapping_device/pull/14) ([st0012](https://github.com/st0012))
139
+
140
+ ## [v0.4.3](https://github.com/st0012/tapping_device/tree/v0.4.3) (2019-12-09)
141
+
142
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.2...v0.4.3)
143
+
144
+ ## [v0.4.2](https://github.com/st0012/tapping_device/tree/v0.4.2) (2019-12-09)
145
+
146
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.1...v0.4.2)
147
+
148
+ **Merged pull requests:**
149
+
150
+ - Refactor tap\_sql! [\#13](https://github.com/st0012/tapping_device/pull/13) ([st0012](https://github.com/st0012))
151
+ - Improve tap sql [\#12](https://github.com/st0012/tapping_device/pull/12) ([st0012](https://github.com/st0012))
152
+
153
+ ## [v0.4.1](https://github.com/st0012/tapping_device/tree/v0.4.1) (2019-12-06)
154
+
155
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.4.0...v0.4.1)
156
+
157
+ **Merged pull requests:**
158
+
159
+ - Add TappingDevice::Payload class [\#11](https://github.com/st0012/tapping_device/pull/11) ([st0012](https://github.com/st0012))
160
+
161
+ ## [v0.4.0](https://github.com/st0012/tapping_device/tree/v0.4.0) (2019-11-25)
162
+
163
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.3.0...v0.4.0)
164
+
165
+ **Merged pull requests:**
166
+
167
+ - Support tap\_sql! [\#10](https://github.com/st0012/tapping_device/pull/10) ([st0012](https://github.com/st0012))
168
+ - Minor adjustment [\#9](https://github.com/st0012/tapping_device/pull/9) ([NickWarm](https://github.com/NickWarm))
169
+
170
+ ## [v0.3.0](https://github.com/st0012/tapping_device/tree/v0.3.0) (2019-11-03)
171
+
172
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.2.0...v0.3.0)
173
+
174
+ **Implemented enhancements:**
175
+
176
+ - Largely improve performance by fixing bad design [\#8](https://github.com/st0012/tapping_device/pull/8) ([st0012](https://github.com/st0012))
177
+
178
+ ## [v0.2.0](https://github.com/st0012/tapping_device/tree/v0.2.0) (2019-11-02)
179
+
180
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.1.1...v0.2.0)
181
+
182
+ **Implemented enhancements:**
183
+
184
+ - Add Device class [\#3](https://github.com/st0012/tapping_device/pull/3) ([st0012](https://github.com/st0012))
185
+
186
+ **Merged pull requests:**
187
+
188
+ - Remove tapping\_deivce/device.rb [\#7](https://github.com/st0012/tapping_device/pull/7) ([st0012](https://github.com/st0012))
189
+ - Reduce namespace [\#6](https://github.com/st0012/tapping_device/pull/6) ([st0012](https://github.com/st0012))
190
+ - Register and control all devices from Device class [\#5](https://github.com/st0012/tapping_device/pull/5) ([st0012](https://github.com/st0012))
191
+ - Support `TappingDevice::Device\#stop\_when` [\#4](https://github.com/st0012/tapping_device/pull/4) ([st0012](https://github.com/st0012))
192
+
193
+ ## [v0.1.1](https://github.com/st0012/tapping_device/tree/v0.1.1) (2019-10-20)
194
+
195
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/v0.1.0...v0.1.1)
196
+
197
+ **Implemented enhancements:**
198
+
199
+ - More filters, Refactoring and Readme update [\#2](https://github.com/st0012/tapping_device/pull/2) ([st0012](https://github.com/st0012))
200
+ - Support tapping ActiveRecord class/instance [\#1](https://github.com/st0012/tapping_device/pull/1) ([st0012](https://github.com/st0012))
201
+
202
+ ## [v0.1.0](https://github.com/st0012/tapping_device/tree/v0.1.0) (2019-10-19)
203
+
204
+ [Full Changelog](https://github.com/st0012/tapping_device/compare/61039c87a55c664661b788e24311e263b28a3ee8...v0.1.0)
205
+
206
+
207
+
208
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
@@ -1,19 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tapping_device (0.5.2)
4
+ tapping_device (0.5.3)
5
5
  activerecord (>= 5.2)
6
+ activesupport
6
7
  pry
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
- activemodel (6.0.3.1)
12
- activesupport (= 6.0.3.1)
13
- activerecord (6.0.3.1)
14
- activemodel (= 6.0.3.1)
15
- activesupport (= 6.0.3.1)
16
- activesupport (6.0.3.1)
12
+ activemodel (6.0.3.2)
13
+ activesupport (= 6.0.3.2)
14
+ activerecord (6.0.3.2)
15
+ activemodel (= 6.0.3.2)
16
+ activesupport (= 6.0.3.2)
17
+ activesupport (6.0.3.2)
17
18
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
19
  i18n (>= 0.7, < 2)
19
20
  minitest (~> 5.1)
data/README.md CHANGED
@@ -174,6 +174,20 @@ Now you can see what method changes which states. And more importantly, you get
174
174
 
175
175
  **You can try these examples on [my fork of discourse](https://github.com/st0012/discourse/tree/demo-for-tapping-device)**
176
176
 
177
+ ### `write_*` helpers
178
+
179
+ `tapping_device` also provides helpers that write the events into files:
180
+
181
+ - `write_calls(object)`
182
+ - `write_traces(object)`
183
+ - `write_mutations(object)`
184
+
185
+ The default destination is `/tmp/tapping_device.log`. You can change it with the `log_file` option:
186
+
187
+ ```ruby
188
+ write_calls(object, log_file: "/tmp/another_file")
189
+ ```
190
+
177
191
 
178
192
  ## Installation
179
193
  Add this line to your application's Gemfile:
@@ -197,9 +211,9 @@ $ gem install tapping_device
197
211
  **Depending on the size of your application, `TappingDevice` could harm the performance significantly. So make sure you don't put it inside the production group**
198
212
 
199
213
 
200
- ### Advance Usages & Options
214
+ ## Advance Usages & Options
201
215
 
202
- #### Add Conditions With `.with`
216
+ ### Add Conditions With `.with`
203
217
 
204
218
  Sometimes we don't need to know all the calls or traces of an object; we just want some of them. In those cases, we can chain the helpers with `.with` to filter the calls/traces.
205
219
 
@@ -210,7 +224,29 @@ print_calls(object).with do |payload|
210
224
  end
211
225
  ```
212
226
 
213
- #### `colorize: false`
227
+ ### Options
228
+
229
+ There are many options you can pass when using a helper method. You can list all available options and their default value with
230
+
231
+ ```ruby
232
+ TappingDevice::Configurable::DEFAULTS #=> {
233
+ :filter_by_paths=>[],
234
+ :exclude_by_paths=>[],
235
+ :with_trace_to=>50,
236
+ :event_type=>:return,
237
+ :hijack_attr_methods=>false,
238
+ :track_as_records=>false,
239
+ :inspect=>false,
240
+ :colorize=>true,
241
+ :log_file=>"/tmp/tapping_device.log"
242
+ }
243
+ ```
244
+
245
+ Here are some commonly used options:
246
+
247
+ #### `colorize: false`
248
+
249
+ - default: `true`
214
250
 
215
251
  By default `print_calls` and `print_traces` colorize their output. If you don't want the colors, you can use `colorize: false` to disable it.
216
252
 
@@ -220,7 +256,9 @@ print_calls(object, colorize: false)
220
256
  ```
221
257
 
222
258
 
223
- #### `inspect: true`
259
+ #### `inspect: true`
260
+
261
+ - default: `false`
224
262
 
225
263
  As you might have noticed, all the objects are converted into strings with `#to_s` instead of `#inspect`. This is because when used on some Rails objects, `#inspect` can generate a significantly larger string than `#to_s`. For example:
226
264
 
@@ -229,6 +267,53 @@ post.to_s #=> #
229
267
  post.inspect #=> #<Post id: 649, user_id: 3, topic_id: 600, post_number: 1, raw: "Hello world", cooked: "<p>Hello world</p>", created_at: "2020-05-24 08:07:29", updated_at: "2020-05-24 08:07:29", reply_to_post_number: nil, reply_count: 0, quote_count: 0, deleted_at: nil, off_topic_count: 0, like_count: 0, incoming_link_count: 0, bookmark_count: 0, score: nil, reads: 0, post_type: 1, sort_order: 1, last_editor_id: 3, hidden: false, hidden_reason_id: nil, notify_moderators_count: 0, spam_count: 0, illegal_count: 0, inappropriate_count: 0, last_version_at: "2020-05-24 08:07:29", user_deleted: false, reply_to_user_id: nil, percent_rank: 1.0, notify_user_count: 0, like_score: 0, deleted_by_id: nil, edit_reason: nil, word_count: 2, version: 1, cook_method: 1, wiki: false, baked_at: "2020-05-24 08:07:29", baked_version: 2, hidden_at: nil, self_edits: 0, reply_quoted: false, via_email: false, raw_email: nil, public_version: 1, action_code: nil, image_url: nil, locked_by_id: nil, image_upload_id: nil>
230
268
  ```
231
269
 
270
+ #### `hijack_attr_methods: true`
271
+
272
+ - default: `false`
273
+ - except for `tap_mutation!` and `print_mutations`
274
+
275
+ Because `TracePoint` doesn't track methods generated by `attr_*` helpers (see [this issue](https://bugs.ruby-lang.org/issues/16383) for more info), we need to redefine those methods with the normal method definition.
276
+
277
+ For example, it generates
278
+
279
+ ```ruby
280
+ def name=(val)
281
+ @name = val
282
+ end
283
+ ```
284
+
285
+ for
286
+
287
+ ```ruby
288
+ attr_writer :name
289
+ ```
290
+
291
+ This hack will only be applied to the target instance with `instance_eval`. So other instances of the class remain untouched.
292
+
293
+ The default is `false` because
294
+
295
+ 1. Checking what methods are generated by `attr_*` helpers isn't free. It's an `O(n)` operation, where `n` is the number of methods the target object has.
296
+ 2. It's still unclear if this hack safe enough for most applications.
297
+
298
+
299
+ ### Global Configuration
300
+
301
+ If you don't want to pass options every time you use a helper, you can use global configuration to change the default values:
302
+
303
+ ```ruby
304
+ TappingDevice.config[:colorize] = false
305
+ TappingDevice.config[:hijack_attr_methods] = true
306
+ ```
307
+
308
+ And if you're using Rails, you can put the configs under `config/initializers/tapping_device.rb` like this:
309
+
310
+ ```ruby
311
+ if defined?(TappingDevice)
312
+ TappingDevice.config[:colorize] = false
313
+ TappingDevice.config[:hijack_attr_methods] = true
314
+ end
315
+ ```
316
+
232
317
 
233
318
  ### Lower-Level Helpers
234
319
  `print_calls` and `print_traces` aren't the only helpers you can get from `TappingDevice`. They are actually built on top of other helpers, which you can use as well. To know more about them, please check [this page](https://github.com/st0012/tapping_device/wiki/Advance-Usages)
@@ -256,4 +341,3 @@ The gem is available as open-source under the terms of the [MIT License](https:/
256
341
  ## Code of Conduct
257
342
 
258
343
  Everyone interacting in the TappingDevice project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/tapping_device/blob/master/CODE_OF_CONDUCT.md).
259
-
@@ -1,10 +1,15 @@
1
1
  require "active_record"
2
+ require "active_support/core_ext/module/introspection"
3
+ require "pry" # for using Method#source
4
+
2
5
  require "tapping_device/version"
3
6
  require "tapping_device/manageable"
4
7
  require "tapping_device/payload"
5
- require "tapping_device/output_payload"
8
+ require "tapping_device/output"
6
9
  require "tapping_device/trackable"
10
+ require "tapping_device/configurable"
7
11
  require "tapping_device/exceptions"
12
+ require "tapping_device/method_hijacker"
8
13
  require "tapping_device/trackers/initialization_tracker"
9
14
  require "tapping_device/trackers/passed_tracker"
10
15
  require "tapping_device/trackers/association_call_tracker"
@@ -23,6 +28,9 @@ class TappingDevice
23
28
 
24
29
  extend Manageable
25
30
 
31
+ include Configurable
32
+ include Output::Helpers
33
+
26
34
  def initialize(options = {}, &block)
27
35
  @block = block
28
36
  @output_block = nil
@@ -33,19 +41,6 @@ class TappingDevice
33
41
  TappingDevice.devices << self
34
42
  end
35
43
 
36
- def and_print(payload_method = nil, &block)
37
- @output_block =
38
- if block
39
- -> (output_payload) { puts(block.call(output_payload)) }
40
- elsif payload_method
41
- -> (output_payload) { puts(output_payload.send(payload_method)) }
42
- else
43
- raise "need to provide either a payload method name or a block"
44
- end
45
-
46
- self
47
- end
48
-
49
44
  def with(&block)
50
45
  @with_condition = block
51
46
  end
@@ -83,6 +78,8 @@ class TappingDevice
83
78
  @target = object
84
79
  validate_target!
85
80
 
81
+ MethodHijacker.new(@target).hijack_methods! if options[:hijack_attr_methods]
82
+
86
83
  @trace_point = build_minimum_trace_point(event_type: options[:event_type]) do |payload|
87
84
  record_call!(payload)
88
85
 
@@ -130,7 +127,7 @@ class TappingDevice
130
127
 
131
128
  if Module.respond_to?(:module_parents)
132
129
  tp.defined_class.module_parents.include?(TappingDevice)
133
- else
130
+ elsif Module.respond_to?(:parents)
134
131
  tp.defined_class.parents.include?(TappingDevice)
135
132
  end
136
133
  end
@@ -201,13 +198,15 @@ class TappingDevice
201
198
  end
202
199
 
203
200
  def process_options(options)
204
- options[:filter_by_paths] ||= []
205
- options[:exclude_by_paths] ||= []
206
- options[:with_trace_to] ||= 50
207
- options[:root_device] ||= self
208
- options[:event_type] ||= :return
201
+ options[:filter_by_paths] ||= config[:filter_by_paths]
202
+ options[:exclude_by_paths] ||= config[:exclude_by_paths]
203
+ options[:with_trace_to] ||= config[:with_trace_to]
204
+ options[:event_type] ||= config[:event_type]
205
+ options[:hijack_attr_methods] ||= config[:hijack_attr_methods]
206
+ options[:track_as_records] ||= config[:track_as_records]
207
+
209
208
  options[:descendants] ||= []
210
- options[:track_as_records] ||= false
209
+ options[:root_device] ||= self
211
210
  options
212
211
  end
213
212
 
@@ -227,7 +226,7 @@ class TappingDevice
227
226
  def record_call!(payload)
228
227
  return if @disabled
229
228
 
230
- @output_block.call(OutputPayload.init(payload)) if @output_block
229
+ write_output!(payload) if @output_writer
231
230
 
232
231
  if @block
233
232
  root_device.calls << @block.call(payload)
@@ -236,10 +235,18 @@ class TappingDevice
236
235
  end
237
236
  end
238
237
 
238
+ def write_output!(payload)
239
+ @output_writer.write!(payload)
240
+ end
241
+
239
242
  def stop_if_condition_fulfilled!(payload)
240
243
  if @stop_when&.call(payload)
241
244
  stop!
242
245
  root_device.stop!
243
246
  end
244
247
  end
248
+
249
+ def config
250
+ TappingDevice.config
251
+ end
245
252
  end
@@ -0,0 +1,25 @@
1
+ require "active_support/configurable"
2
+ require "active_support/concern"
3
+
4
+ class TappingDevice
5
+ module Configurable
6
+ extend ActiveSupport::Concern
7
+
8
+ DEFAULTS = {
9
+ filter_by_paths: [],
10
+ exclude_by_paths: [],
11
+ with_trace_to: 50,
12
+ event_type: :return,
13
+ hijack_attr_methods: false,
14
+ track_as_records: false,
15
+ }.merge(TappingDevice::Output::DEFAULT_OPTIONS)
16
+
17
+ included do
18
+ include ActiveSupport::Configurable
19
+
20
+ DEFAULTS.each do |key, value|
21
+ config[key] = value
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,51 @@
1
+ class TappingDevice
2
+ class MethodHijacker
3
+ attr_reader :target
4
+
5
+ def initialize(target)
6
+ @target = target
7
+ end
8
+
9
+ def hijack_methods!
10
+ target.methods.each do |method_name|
11
+ if is_writer_method?(method_name)
12
+ redefine_writer_method!(method_name)
13
+ elsif is_reader_method?(method_name)
14
+ redefine_reader_method!(method_name)
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def is_writer_method?(method_name)
22
+ has_definition_source?(method_name) && method_name.match?(/\w+=/) && target.method(method_name).source.match?(/attr_writer|attr_accessor/)
23
+ end
24
+
25
+ def is_reader_method?(method_name)
26
+ has_definition_source?(method_name) && target.method(method_name).source.match?(/attr_reader|attr_accessor/)
27
+ end
28
+
29
+ def has_definition_source?(method_name)
30
+ target.method(method_name).source_location
31
+ end
32
+
33
+ def redefine_writer_method!(method_name)
34
+ ivar_name = "@#{method_name.to_s.sub("=", "")}"
35
+
36
+ target.instance_eval <<-RUBY, __FILE__, __LINE__ + 1
37
+ def #{method_name}(val)
38
+ #{ivar_name} = val
39
+ end
40
+ RUBY
41
+ end
42
+
43
+ def redefine_reader_method!(method_name)
44
+ target.instance_eval <<-RUBY, __FILE__, __LINE__ + 1
45
+ def #{method_name}
46
+ @#{method_name}
47
+ end
48
+ RUBY
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,42 @@
1
+ require "tapping_device/output/payload"
2
+ require "tapping_device/output/writer"
3
+ require "tapping_device/output/stdout_writer"
4
+ require "tapping_device/output/file_writer"
5
+
6
+ class TappingDevice
7
+ module Output
8
+ DEFAULT_OPTIONS = {
9
+ inspect: false,
10
+ colorize: true,
11
+ log_file: "/tmp/tapping_device.log"
12
+ }
13
+
14
+ module Helpers
15
+ def and_write(payload_method = nil, options: {}, &block)
16
+ and_output(payload_method, options: options, writer_klass: FileWriter, &block)
17
+ end
18
+
19
+ def and_print(payload_method = nil, options: {}, &block)
20
+ and_output(payload_method, options: options, writer_klass: StdoutWriter, &block)
21
+ end
22
+
23
+ def and_output(payload_method = nil, options: {}, writer_klass:, &block)
24
+ output_block = generate_output_block(payload_method, block)
25
+ @output_writer = writer_klass.new(options, output_block)
26
+ self
27
+ end
28
+
29
+ private
30
+
31
+ def generate_output_block(payload_method, block)
32
+ if block
33
+ block
34
+ elsif payload_method
35
+ -> (output_payload, output_options) { output_payload.send(payload_method, output_options) }
36
+ else
37
+ raise "need to provide either a payload method name or a block"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ class TappingDevice
2
+ module Output
3
+ class FileWriter < Writer
4
+ def initialize(options, output_block)
5
+ @path = options[:log_file]
6
+
7
+ File.write(@path, "") # clean file
8
+
9
+ super
10
+ end
11
+
12
+ def write!(payload)
13
+ output = generate_output(payload)
14
+
15
+ File.open(@path, "a") do |f|
16
+ f << output
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,175 @@
1
+ class TappingDevice
2
+ module Output
3
+ class Payload < Payload
4
+ UNDEFINED = "[undefined]"
5
+
6
+ alias :raw_arguments :arguments
7
+ alias :raw_return_value :return_value
8
+
9
+ def method_name(options = {})
10
+ ":#{super(options)}"
11
+ end
12
+
13
+ def arguments(options = {})
14
+ generate_string_result(raw_arguments, options[:inspect])
15
+ end
16
+
17
+ def return_value(options = {})
18
+ generate_string_result(raw_return_value, options[:inspect])
19
+ end
20
+
21
+ COLOR_CODES = {
22
+ green: 10,
23
+ yellow: 11,
24
+ blue: 12,
25
+ megenta: 13,
26
+ cyan: 14,
27
+ orange: 214
28
+ }
29
+
30
+ COLORS = COLOR_CODES.each_with_object({}) do |(name, code), hash|
31
+ hash[name] = "\u001b[38;5;#{code}m"
32
+ end.merge(
33
+ reset: "\u001b[0m",
34
+ nocolor: ""
35
+ )
36
+
37
+ PAYLOAD_ATTRIBUTES = {
38
+ method_name: {symbol: "", color: COLORS[:blue]},
39
+ location: {symbol: "from:", color: COLORS[:green]},
40
+ return_value: {symbol: "=>", color: COLORS[:megenta]},
41
+ arguments: {symbol: "<=", color: COLORS[:orange]},
42
+ ivar_changes: {symbol: "changes:\n", color: COLORS[:blue]},
43
+ defined_class: {symbol: "#", color: COLORS[:yellow]}
44
+ }
45
+
46
+ PAYLOAD_ATTRIBUTES.each do |attribute, attribute_options|
47
+ color = attribute_options[:color]
48
+
49
+ alias_method "original_#{attribute}".to_sym, attribute
50
+
51
+ # regenerate attributes with `colorize: true` support
52
+ define_method attribute do |options = {}|
53
+ call_result = send("original_#{attribute}", options)
54
+
55
+ if options[:colorize]
56
+ "#{color}#{call_result}#{COLORS[:reset]}"
57
+ else
58
+ call_result
59
+ end
60
+ end
61
+
62
+ define_method "#{attribute}_with_color" do |options = {}|
63
+ send(attribute, options.merge(colorize: true))
64
+ end
65
+
66
+ PAYLOAD_ATTRIBUTES.each do |and_attribute, and_attribute_options|
67
+ next if and_attribute == attribute
68
+
69
+ define_method "#{attribute}_and_#{and_attribute}" do |options = {}|
70
+ "#{send(attribute, options)} #{and_attribute_options[:symbol]} #{send(and_attribute, options)}"
71
+ end
72
+
73
+ define_method "#{attribute}_and_#{and_attribute}_with_color" do |options = {}|
74
+ send("#{attribute}_and_#{and_attribute}", options.merge(colorize: true))
75
+ end
76
+ end
77
+ end
78
+
79
+ def passed_at(options = {})
80
+ with_method_head = options.fetch(:with_method_head, false)
81
+ arg_name = raw_arguments.keys.detect { |k| raw_arguments[k] == target }
82
+
83
+ return unless arg_name
84
+
85
+ arg_name = ":#{arg_name}"
86
+ arg_name = value_with_color(arg_name, :orange) if options[:colorize]
87
+ msg = "Passed as #{arg_name} in '#{defined_class(options)}##{method_name(options)}' at #{location(options)}\n"
88
+ msg += " > #{method_head}\n" if with_method_head
89
+ msg
90
+ end
91
+
92
+ def detail_call_info(options = {})
93
+ <<~MSG
94
+ #{method_name_and_defined_class(options)}
95
+ from: #{location(options)}
96
+ <= #{arguments(options)}
97
+ => #{return_value(options)}
98
+
99
+ MSG
100
+ end
101
+
102
+ def ivar_changes(options = {})
103
+ super.map do |ivar, value_changes|
104
+ before = generate_string_result(value_changes[:before], options[:inspect])
105
+ after = generate_string_result(value_changes[:after], options[:inspect])
106
+
107
+ if options[:colorize]
108
+ ivar = "#{COLORS[:orange]}#{ivar}#{COLORS[:reset]}"
109
+ before = "#{COLORS[:blue]}#{before.to_s}#{COLORS[:reset]}"
110
+ after = "#{COLORS[:blue]}#{after.to_s}#{COLORS[:reset]}"
111
+ end
112
+
113
+ " #{ivar}: #{before.to_s} => #{after.to_s}"
114
+ end.join("\n")
115
+ end
116
+
117
+ def call_info_with_ivar_changes(options = {})
118
+ <<~MSG
119
+ #{method_name_and_defined_class(options)}
120
+ from: #{location(options)}
121
+ changes:
122
+ #{ivar_changes(options)}
123
+
124
+ MSG
125
+ end
126
+
127
+ private
128
+
129
+ def value_with_color(value, color)
130
+ "#{COLORS[color]}#{value}#{COLORS[:reset]}"
131
+ end
132
+
133
+ def generate_string_result(obj, inspect)
134
+ case obj
135
+ when Array
136
+ array_to_string(obj, inspect)
137
+ when Hash
138
+ hash_to_string(obj, inspect)
139
+ when UNDEFINED
140
+ UNDEFINED
141
+ when String
142
+ "\"#{obj}\""
143
+ when nil
144
+ "nil"
145
+ else
146
+ inspect ? obj.inspect : obj.to_s
147
+ end
148
+ end
149
+
150
+ def array_to_string(array, inspect)
151
+ elements_string = array.map do |elem|
152
+ generate_string_result(elem, inspect)
153
+ end.join(", ")
154
+ "[#{elements_string}]"
155
+ end
156
+
157
+ def hash_to_string(hash, inspect)
158
+ elements_string = hash.map do |key, value|
159
+ "#{key.to_s}: #{generate_string_result(value, inspect)}"
160
+ end.join(", ")
161
+ "{#{elements_string}}"
162
+ end
163
+
164
+ def obj_to_string(element, inspect)
165
+ to_string_method = inspect ? :inspect : :to_s
166
+
167
+ if !inspect && element.is_a?(String)
168
+ "\"#{element}\""
169
+ else
170
+ element.send(to_string_method)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,9 @@
1
+ class TappingDevice
2
+ module Output
3
+ class StdoutWriter < Writer
4
+ def write!(payload)
5
+ puts(generate_output(payload))
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ class TappingDevice
2
+ module Output
3
+ class Writer
4
+ def initialize(options, output_block)
5
+ @options = options
6
+ @output_block = output_block
7
+ end
8
+
9
+ def write!(payload)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ private
14
+
15
+ def generate_output(payload)
16
+ @output_block.call(Output::Payload.init(payload), @options)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -20,8 +20,7 @@ class TappingDevice
20
20
  end
21
21
 
22
22
  def method_head
23
- source_file, source_line = method_object.source_location
24
- IO.readlines(source_file)[source_line-1]
23
+ method_object.source.strip if method_object.source_location
25
24
  end
26
25
 
27
26
  def location(options = {})
@@ -21,38 +21,67 @@ class TappingDevice
21
21
  end
22
22
 
23
23
  def print_traces(target, options = {})
24
- output_options = extract_output_options(options)
25
- options[:event_type] = :call
24
+ output_traces(target, options, output_action: :and_print)
25
+ end
26
26
 
27
- device_1 = tap_on!(target, options).and_print do |output_payload|
28
- "Called #{output_payload.method_name_and_location(output_options)}"
29
- end
30
- device_2 = tap_passed!(target, options).and_print do |output_payload|
31
- output_payload.passed_at(output_options)
32
- end
33
- CollectionProxy.new([device_1, device_2])
27
+ def write_traces(target, options = {})
28
+ output_traces(target, options, output_action: :and_write)
34
29
  end
35
30
 
36
31
  def print_calls(target, options = {})
37
- output_options = extract_output_options(options)
32
+ output_calls(target, options, output_action: :and_print)
33
+ end
34
+
35
+ def write_calls(target, options = {})
36
+ output_calls(target, options, output_action: :and_write)
37
+ end
38
+
39
+ def print_mutations(target, options = {})
40
+ output_mutations(target, options, output_action: :and_print)
41
+ end
42
+
43
+ def write_mutations(target, options = {})
44
+ output_mutations(target, options, output_action: :and_write)
45
+ end
38
46
 
39
- tap_on!(target, options).and_print do |output_payload|
47
+ private
48
+
49
+ def output_calls(target, options = {}, output_action:)
50
+ device_options, output_options = separate_options(options)
51
+
52
+ tap_on!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
40
53
  output_payload.detail_call_info(output_options)
41
54
  end
42
55
  end
43
56
 
44
- def print_mutations(target, options = {})
45
- output_options = extract_output_options(options)
57
+ def output_traces(target, options = {}, output_action:)
58
+ device_options, output_options = separate_options(options)
59
+ device_options[:event_type] = :call
46
60
 
47
- tap_mutation!(target, options).and_print do |output_payload|
61
+ device_1 = tap_on!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
62
+ "Called #{output_payload.method_name_and_location(output_options)}\n"
63
+ end
64
+ device_2 = tap_passed!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
65
+ output_payload.passed_at(output_options)
66
+ end
67
+ CollectionProxy.new([device_1, device_2])
68
+ end
69
+
70
+ def output_mutations(target, options = {}, output_action:)
71
+ device_options, output_options = separate_options(options)
72
+
73
+ tap_mutation!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
48
74
  output_payload.call_info_with_ivar_changes(output_options)
49
75
  end
50
76
  end
51
77
 
52
- private
78
+ def separate_options(options)
79
+ output_options = Output::DEFAULT_OPTIONS.keys.each_with_object({}) do |key, hash|
80
+ hash[key] = options.fetch(key, TappingDevice.config[key])
81
+ options.delete(key)
82
+ end
53
83
 
54
- def extract_output_options(options)
55
- {inspect: options.delete(:inspect), colorize: options.fetch(:colorize, true)}
84
+ [options, output_options]
56
85
  end
57
86
 
58
87
  class CollectionProxy
@@ -1,16 +1,14 @@
1
- require "pry" # for using Method#source
2
-
3
1
  class TappingDevice
4
2
  module Trackers
5
3
  class MutationTracker < TappingDevice
6
4
  def initialize(options, &block)
5
+ options[:hijack_attr_methods] = true
7
6
  super
8
7
  @snapshot_stack = []
9
8
  end
10
9
 
11
10
  def track(object)
12
11
  super
13
- hijack_attr_writers
14
12
  insert_snapshot_taking_trace_point
15
13
  self
16
14
  end
@@ -45,24 +43,6 @@ class TappingDevice
45
43
  end
46
44
  end
47
45
 
48
- def hijack_attr_writers
49
- writer_methods = target.methods.grep(/\w+=/)
50
- writer_methods.each do |method_name|
51
- if target.method(method_name).source.match?(/attr_writer|attr_accessor/)
52
- ivar_name = "@#{method_name.to_s.sub("=", "")}"
53
-
54
- # need to use instance_eval to make the call site location consistent with normal methods
55
- target.instance_eval(
56
- <<~CODE
57
- def #{method_name}(val)
58
- #{ivar_name} = val
59
- end
60
- CODE
61
- )
62
- end
63
- end
64
- end
65
-
66
46
  def build_payload(tp:, filepath:, line_number:)
67
47
  payload = super
68
48
 
@@ -78,12 +58,12 @@ class TappingDevice
78
58
 
79
59
  additional_keys = @latest_instance_variables.keys - @instance_variables_snapshot.keys
80
60
  additional_keys.each do |key|
81
- changes[key] = {before: OutputPayload::UNDEFINED, after: @latest_instance_variables[key]}
61
+ changes[key] = {before: Output::Payload::UNDEFINED, after: @latest_instance_variables[key]}
82
62
  end
83
63
 
84
64
  removed_keys = @instance_variables_snapshot.keys - @latest_instance_variables.keys
85
65
  removed_keys.each do |key|
86
- changes[key] = {before: @instance_variables_snapshot[key], after: OutputPayload::UNDEFINED}
66
+ changes[key] = {before: @instance_variables_snapshot[key], after: Output::Payload::UNDEFINED}
87
67
  end
88
68
 
89
69
  remained_keys = @latest_instance_variables.keys - additional_keys
@@ -1,3 +1,3 @@
1
1
  class TappingDevice
2
- VERSION = "0.5.2"
2
+ VERSION = "0.5.3"
3
3
  end
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
33
33
  end
34
34
 
35
35
  spec.add_dependency "pry" # for using Method#source in MutationTracker
36
+ spec.add_dependency "activesupport"
36
37
 
37
38
  spec.add_development_dependency "sqlite3", ">= 1.3.6"
38
39
  spec.add_development_dependency "database_cleaner"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapping_device
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - st0012
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-10 00:00:00.000000000 Z
11
+ date: 2020-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: sqlite3
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +151,7 @@ files:
137
151
  - ".rspec"
138
152
  - ".ruby-version"
139
153
  - ".travis.yml"
154
+ - CHANGELOG.md
140
155
  - CODE_OF_CONDUCT.md
141
156
  - Gemfile
142
157
  - Gemfile.lock
@@ -150,9 +165,15 @@ files:
150
165
  - images/print_mutations.png
151
166
  - images/print_traces.png
152
167
  - lib/tapping_device.rb
168
+ - lib/tapping_device/configurable.rb
153
169
  - lib/tapping_device/exceptions.rb
154
170
  - lib/tapping_device/manageable.rb
155
- - lib/tapping_device/output_payload.rb
171
+ - lib/tapping_device/method_hijacker.rb
172
+ - lib/tapping_device/output.rb
173
+ - lib/tapping_device/output/file_writer.rb
174
+ - lib/tapping_device/output/payload.rb
175
+ - lib/tapping_device/output/stdout_writer.rb
176
+ - lib/tapping_device/output/writer.rb
156
177
  - lib/tapping_device/payload.rb
157
178
  - lib/tapping_device/trackable.rb
158
179
  - lib/tapping_device/trackers/association_call_tracker.rb
@@ -1,173 +0,0 @@
1
- class TappingDevice
2
- class OutputPayload < Payload
3
- UNDEFINED = "[undefined]"
4
-
5
- alias :raw_arguments :arguments
6
- alias :raw_return_value :return_value
7
-
8
- def method_name(options = {})
9
- ":#{super(options)}"
10
- end
11
-
12
- def arguments(options = {})
13
- generate_string_result(raw_arguments, options[:inspect])
14
- end
15
-
16
- def return_value(options = {})
17
- generate_string_result(raw_return_value, options[:inspect])
18
- end
19
-
20
- COLOR_CODES = {
21
- green: 10,
22
- yellow: 11,
23
- blue: 12,
24
- megenta: 13,
25
- cyan: 14,
26
- orange: 214
27
- }
28
-
29
- COLORS = COLOR_CODES.each_with_object({}) do |(name, code), hash|
30
- hash[name] = "\u001b[38;5;#{code}m"
31
- end.merge(
32
- reset: "\u001b[0m",
33
- nocolor: ""
34
- )
35
-
36
- PAYLOAD_ATTRIBUTES = {
37
- method_name: {symbol: "", color: COLORS[:blue]},
38
- location: {symbol: "from:", color: COLORS[:green]},
39
- return_value: {symbol: "=>", color: COLORS[:megenta]},
40
- arguments: {symbol: "<=", color: COLORS[:orange]},
41
- ivar_changes: {symbol: "changes:\n", color: COLORS[:blue]},
42
- defined_class: {symbol: "#", color: COLORS[:yellow]}
43
- }
44
-
45
- PAYLOAD_ATTRIBUTES.each do |attribute, attribute_options|
46
- color = attribute_options[:color]
47
-
48
- alias_method "original_#{attribute}".to_sym, attribute
49
-
50
- # regenerate attributes with `colorize: true` support
51
- define_method attribute do |options = {}|
52
- call_result = send("original_#{attribute}", options)
53
-
54
- if options[:colorize]
55
- "#{color}#{call_result}#{COLORS[:reset]}"
56
- else
57
- call_result
58
- end
59
- end
60
-
61
- define_method "#{attribute}_with_color" do |options = {}|
62
- send(attribute, options.merge(colorize: true))
63
- end
64
-
65
- PAYLOAD_ATTRIBUTES.each do |and_attribute, and_attribute_options|
66
- next if and_attribute == attribute
67
-
68
- define_method "#{attribute}_and_#{and_attribute}" do |options = {}|
69
- "#{send(attribute, options)} #{and_attribute_options[:symbol]} #{send(and_attribute, options)}"
70
- end
71
-
72
- define_method "#{attribute}_and_#{and_attribute}_with_color" do |options = {}|
73
- send("#{attribute}_and_#{and_attribute}", options.merge(colorize: true))
74
- end
75
- end
76
- end
77
-
78
- def passed_at(options = {})
79
- with_method_head = options.fetch(:with_method_head, false)
80
- arg_name = raw_arguments.keys.detect { |k| raw_arguments[k] == target }
81
-
82
- return unless arg_name
83
-
84
- arg_name = ":#{arg_name}"
85
- arg_name = value_with_color(arg_name, :orange) if options[:colorize]
86
- msg = "Passed as #{arg_name} in '#{defined_class(options)}##{method_name(options)}' at #{location(options)}"
87
- msg += "\n > #{method_head.strip}" if with_method_head
88
- msg
89
- end
90
-
91
- def detail_call_info(options = {})
92
- <<~MSG
93
- #{method_name_and_defined_class(options)}
94
- from: #{location(options)}
95
- <= #{arguments(options)}
96
- => #{return_value(options)}
97
-
98
- MSG
99
- end
100
-
101
- def ivar_changes(options = {})
102
- super.map do |ivar, value_changes|
103
- before = generate_string_result(value_changes[:before], options[:inspect])
104
- after = generate_string_result(value_changes[:after], options[:inspect])
105
-
106
- if options[:colorize]
107
- ivar = "#{COLORS[:orange]}#{ivar}#{COLORS[:reset]}"
108
- before = "#{COLORS[:blue]}#{before.to_s}#{COLORS[:reset]}"
109
- after = "#{COLORS[:blue]}#{after.to_s}#{COLORS[:reset]}"
110
- end
111
-
112
- " #{ivar}: #{before.to_s} => #{after.to_s}"
113
- end.join("\n")
114
- end
115
-
116
- def call_info_with_ivar_changes(options = {})
117
- <<~MSG
118
- #{method_name_and_defined_class(options)}
119
- from: #{location(options)}
120
- changes:
121
- #{ivar_changes(options)}
122
-
123
- MSG
124
- end
125
-
126
- private
127
-
128
- def value_with_color(value, color)
129
- "#{COLORS[color]}#{value}#{COLORS[:reset]}"
130
- end
131
-
132
- def generate_string_result(obj, inspect)
133
- case obj
134
- when Array
135
- array_to_string(obj, inspect)
136
- when Hash
137
- hash_to_string(obj, inspect)
138
- when UNDEFINED
139
- UNDEFINED
140
- when String
141
- "\"#{obj}\""
142
- when nil
143
- "nil"
144
- else
145
- inspect ? obj.inspect : obj.to_s
146
- end
147
- end
148
-
149
- def array_to_string(array, inspect)
150
- elements_string = array.map do |elem|
151
- generate_string_result(elem, inspect)
152
- end.join(", ")
153
- "[#{elements_string}]"
154
- end
155
-
156
- def hash_to_string(hash, inspect)
157
- elements_string = hash.map do |key, value|
158
- "#{key.to_s}: #{generate_string_result(value, inspect)}"
159
- end.join(", ")
160
- "{#{elements_string}}"
161
- end
162
-
163
- def obj_to_string(element, inspect)
164
- to_string_method = inspect ? :inspect : :to_s
165
-
166
- if !inspect && element.is_a?(String)
167
- "\"#{element}\""
168
- else
169
- element.send(to_string_method)
170
- end
171
- end
172
- end
173
- end