tapping_device 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +14 -4
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +70 -1
- data/Gemfile +11 -0
- data/Makefile +3 -0
- data/README.md +100 -133
- data/lib/tapping_device.rb +18 -12
- data/lib/tapping_device/configurable.rb +2 -0
- data/lib/tapping_device/method_hijacker.rb +4 -0
- data/lib/tapping_device/output.rb +6 -7
- data/lib/tapping_device/output/{payload.rb → payload_wrapper.rb} +46 -35
- data/lib/tapping_device/output/writer.rb +5 -3
- data/lib/tapping_device/payload.rb +23 -13
- data/lib/tapping_device/trackable.rb +48 -18
- data/lib/tapping_device/trackers/initialization_tracker.rb +30 -4
- data/lib/tapping_device/trackers/mutation_tracker.rb +3 -4
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +1 -7
- metadata +9 -25
- data/Gemfile.lock +0 -74
- data/lib/tapping_device/output/file_writer.rb +0 -21
- data/lib/tapping_device/output/stdout_writer.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8adc4b6842ade7721eafaf72e9b94c8a6a852c1f9f9beebdf2556b20cef6d4a
|
4
|
+
data.tar.gz: ab22b319f6468837e92f851f00f863cf732dd3c0cc73e286d5f9569d00c34db6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 992f31b79a3c0a8abc8471ae643f02586cf76d505da493cfe66b37338781251c3dfdba43a158776431c93a342e8b9a02baff78118b71c679aba2f9114c65077c
|
7
|
+
data.tar.gz: cad971d539041804a9860225f17ae23754dc46ea6fa5b7dc15d674e5189c081dd4f6f2602eeea9c27e616fb1b959126fe452ef5f641e249d73392782b17a3c2c
|
data/.github/workflows/ruby.yml
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
name: Ruby
|
2
2
|
|
3
|
-
on:
|
3
|
+
on:
|
4
|
+
workflow_dispatch:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- master
|
8
|
+
pull_request:
|
4
9
|
|
5
10
|
jobs:
|
6
11
|
test:
|
@@ -8,9 +13,12 @@ jobs:
|
|
8
13
|
runs-on: ${{ matrix.os }}
|
9
14
|
strategy:
|
10
15
|
matrix:
|
11
|
-
rails_version: ['5.2', '6']
|
12
|
-
ruby_version: ['2.
|
16
|
+
rails_version: ['5.2', '6.0.0', '6.1.0']
|
17
|
+
ruby_version: ['2.7', '3.0']
|
13
18
|
os: [ubuntu-latest]
|
19
|
+
exclude:
|
20
|
+
- ruby_version: '3.0'
|
21
|
+
rails_version: '5.2'
|
14
22
|
steps:
|
15
23
|
- uses: actions/checkout@v1
|
16
24
|
|
@@ -34,7 +42,9 @@ jobs:
|
|
34
42
|
bundle install --jobs 4 --retry 3
|
35
43
|
|
36
44
|
- name: Run test with Rails ${{ matrix.rails_version }}
|
37
|
-
|
45
|
+
env:
|
46
|
+
RAILS_VERSION: ${{ matrix.rails_version }}
|
47
|
+
run: make test
|
38
48
|
|
39
49
|
- name: Publish Test Coverage
|
40
50
|
uses: paambaati/codeclimate-action@v2.6.0
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.1
|
data/CHANGELOG.md
CHANGED
@@ -2,15 +2,84 @@
|
|
2
2
|
|
3
3
|
## [Unreleased](https://github.com/st0012/tapping_device/tree/HEAD)
|
4
4
|
|
5
|
-
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.
|
5
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.7...HEAD)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Support Ruby 3.0 [\#71](https://github.com/st0012/tapping_device/pull/71) ([st0012](https://github.com/st0012))
|
10
|
+
|
11
|
+
**Merged pull requests:**
|
12
|
+
|
13
|
+
- Drop activerecord requirement [\#73](https://github.com/st0012/tapping_device/pull/73) ([st0012](https://github.com/st0012))
|
14
|
+
- Improve file-writing tests [\#72](https://github.com/st0012/tapping_device/pull/72) ([st0012](https://github.com/st0012))
|
15
|
+
- Simplify output logic with Ruby' Logger class [\#70](https://github.com/st0012/tapping_device/pull/70) ([st0012](https://github.com/st0012))
|
16
|
+
- Refactor Payload classes [\#68](https://github.com/st0012/tapping_device/pull/68) ([st0012](https://github.com/st0012))
|
17
|
+
|
18
|
+
## [v0.5.7](https://github.com/st0012/tapping_device/tree/v0.5.7) (2020-09-09)
|
19
|
+
|
20
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.6...v0.5.7)
|
21
|
+
|
22
|
+
**Closed issues:**
|
23
|
+
|
24
|
+
- Support tag option [\#64](https://github.com/st0012/tapping_device/issues/64)
|
25
|
+
|
26
|
+
**Merged pull requests:**
|
27
|
+
|
28
|
+
- Use pastel to replace handmade colorizing logic [\#66](https://github.com/st0012/tapping_device/pull/66) ([st0012](https://github.com/st0012))
|
29
|
+
- Add tag option [\#65](https://github.com/st0012/tapping_device/pull/65) ([st0012](https://github.com/st0012))
|
30
|
+
|
31
|
+
## [v0.5.6](https://github.com/st0012/tapping_device/tree/v0.5.6) (2020-07-17)
|
32
|
+
|
33
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.5...v0.5.6)
|
34
|
+
|
35
|
+
## [v0.5.5](https://github.com/st0012/tapping_device/tree/v0.5.5) (2020-07-16)
|
36
|
+
|
37
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.4...v0.5.5)
|
38
|
+
|
39
|
+
**Fixed bugs:**
|
40
|
+
|
41
|
+
- InitializationTracker's logic can cause error [\#60](https://github.com/st0012/tapping_device/issues/60)
|
42
|
+
|
43
|
+
**Closed issues:**
|
44
|
+
|
45
|
+
- Refactor get\_method\_from\_object [\#59](https://github.com/st0012/tapping_device/issues/59)
|
46
|
+
|
47
|
+
**Merged pull requests:**
|
48
|
+
|
49
|
+
- Fix init tracker [\#61](https://github.com/st0012/tapping_device/pull/61) ([st0012](https://github.com/st0012))
|
50
|
+
|
51
|
+
## [v0.5.4](https://github.com/st0012/tapping_device/tree/v0.5.4) (2020-07-05)
|
52
|
+
|
53
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.3...v0.5.4)
|
54
|
+
|
55
|
+
**Closed issues:**
|
56
|
+
|
57
|
+
- Add with\_print\_calls method [\#52](https://github.com/st0012/tapping_device/issues/52)
|
58
|
+
- Tapping any instance of class [\#51](https://github.com/st0012/tapping_device/issues/51)
|
59
|
+
- Add ignore\_private option [\#50](https://github.com/st0012/tapping_device/issues/50)
|
60
|
+
|
61
|
+
**Merged pull requests:**
|
62
|
+
|
63
|
+
- Restructure README.md [\#58](https://github.com/st0012/tapping_device/pull/58) ([st0012](https://github.com/st0012))
|
64
|
+
- Better support on private methods [\#57](https://github.com/st0012/tapping_device/pull/57) ([st0012](https://github.com/st0012))
|
65
|
+
- Add with\_\* helpers \(e.g. with\_print\_calls\) [\#56](https://github.com/st0012/tapping_device/pull/56) ([st0012](https://github.com/st0012))
|
66
|
+
- Add force\_recording option for debugging [\#55](https://github.com/st0012/tapping_device/pull/55) ([st0012](https://github.com/st0012))
|
67
|
+
- Add print\_instance\_\* and write\_instance\_\* helpers [\#54](https://github.com/st0012/tapping_device/pull/54) ([st0012](https://github.com/st0012))
|
68
|
+
- Fix tap\_init by adding c\_\* event type [\#53](https://github.com/st0012/tapping_device/pull/53) ([st0012](https://github.com/st0012))
|
69
|
+
|
70
|
+
## [v0.5.3](https://github.com/st0012/tapping_device/tree/v0.5.3) (2020-06-21)
|
71
|
+
|
72
|
+
[Full Changelog](https://github.com/st0012/tapping_device/compare/v0.5.2...v0.5.3)
|
6
73
|
|
7
74
|
**Closed issues:**
|
8
75
|
|
76
|
+
- Global Configuration [\#46](https://github.com/st0012/tapping_device/issues/46)
|
9
77
|
- Support write\_\* helpers [\#44](https://github.com/st0012/tapping_device/issues/44)
|
10
78
|
- Use Method\#source to replace Payload\#method\_head’s implementation [\#19](https://github.com/st0012/tapping_device/issues/19)
|
11
79
|
|
12
80
|
**Merged pull requests:**
|
13
81
|
|
82
|
+
- Support Global Configuration [\#48](https://github.com/st0012/tapping_device/pull/48) ([st0012](https://github.com/st0012))
|
14
83
|
- Support write\_\* helpers [\#47](https://github.com/st0012/tapping_device/pull/47) ([st0012](https://github.com/st0012))
|
15
84
|
- Hijack attr methods with `hijack\_attr\_methods` option [\#45](https://github.com/st0012/tapping_device/pull/45) ([st0012](https://github.com/st0012))
|
16
85
|
|
data/Gemfile
CHANGED
@@ -2,3 +2,14 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in tapping_device.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
rails_version = ENV["RAILS_VERSION"]
|
7
|
+
rails_version = "6.1.0" if rails_version.nil?
|
8
|
+
|
9
|
+
if rails_version.to_f < 6
|
10
|
+
gem "sqlite3", "~> 1.3.0"
|
11
|
+
else
|
12
|
+
gem "sqlite3"
|
13
|
+
end
|
14
|
+
|
15
|
+
gem "activerecord", "~> #{rails_version}"
|
data/Makefile
ADDED
data/README.md
CHANGED
@@ -8,187 +8,119 @@
|
|
8
8
|
|
9
9
|
|
10
10
|
## Introduction
|
11
|
-
|
11
|
+
As the name states, `TappingDevice` allows you to secretly listen to different events of an object:
|
12
12
|
|
13
|
-
|
13
|
+
- `Method Calls` - what does the object do
|
14
|
+
- `Traces` - how is the object used by the application
|
15
|
+
- `State Mutations` - what happens inside the object
|
14
16
|
|
15
|
-
|
17
|
+
After collecting the events, `TappingDevice` will output them in a nice, readable format to either stdout or a file.
|
16
18
|
|
17
|
-
|
18
|
-
- `print_traces(object)` to see how the object interacts with other objects (like used as an argument)
|
19
|
-
- `print_mutations(object)` to see what actions changed the object's state (instance variables)
|
19
|
+
**Ultimately, its goal is to let you know all the information you need for debugging with just 1 line of code.**
|
20
20
|
|
21
|
-
|
21
|
+
## Usages
|
22
22
|
|
23
|
-
###
|
23
|
+
### Track Method Calls
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
```ruby
|
28
|
-
def create
|
29
|
-
@manager_params = create_params
|
30
|
-
@manager_params[:first_post_checks] = !is_api?
|
31
|
-
|
32
|
-
manager = NewPostManager.new(current_user, @manager_params)
|
33
|
-
|
34
|
-
if is_api?
|
35
|
-
memoized_payload = DistributedMemoizer.memoize(signature_for(@manager_params), 120) do
|
36
|
-
result = manager.perform
|
37
|
-
MultiJson.dump(serialize_data(result, NewPostResultSerializer, root: false))
|
38
|
-
end
|
39
|
-
|
40
|
-
parsed_payload = JSON.parse(memoized_payload)
|
41
|
-
backwards_compatible_json(parsed_payload, parsed_payload['success'])
|
42
|
-
else
|
43
|
-
result = manager.perform
|
44
|
-
json = serialize_data(result, NewPostResultSerializer, root: false)
|
45
|
-
backwards_compatible_json(json, result.success?)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
```
|
49
|
-
|
50
|
-
As you can see, it doesn't even exist in the controller action, which makes tracking it by reading code very hard to do.
|
51
|
-
|
52
|
-
But with `TappingDevice`. You can use `print_calls` to show what method calls the object performs
|
53
|
-
|
54
|
-
```ruby
|
55
|
-
def create
|
56
|
-
# you can retrieve the current guardian object by calling guardian in the controller
|
57
|
-
print_calls(guardian)
|
58
|
-
@manager_params = create_params
|
59
|
-
|
60
|
-
# .....
|
61
|
-
```
|
62
|
-
|
63
|
-
Now, if you execute the code, like via tests:
|
64
|
-
|
65
|
-
```shell
|
66
|
-
$ rspec spec/requests/posts_controller_spec.rb:603
|
67
|
-
```
|
68
|
-
|
69
|
-
You can get all the method calls it performs with basically everything you need to know
|
25
|
+
By tracking an object's method calls, you'll be able to observe the object's behavior very easily
|
70
26
|
|
71
27
|
<img src="https://github.com/st0012/tapping_device/blob/master/images/print_calls.png" alt="image of print_calls output" width="50%">
|
72
28
|
|
73
|
-
|
74
|
-
- method name
|
75
|
-
-
|
29
|
+
Each entry consists of 5 pieces of information:
|
30
|
+
- method name
|
31
|
+
- source of the method
|
76
32
|
- call site
|
77
33
|
- arguments
|
78
34
|
- return value
|
79
35
|
|
80
36
|
![explanation of individual entry](https://github.com/st0012/tapping_device/blob/master/images/print_calls%20-%20single%20entry.png)
|
81
37
|
|
82
|
-
|
38
|
+
#### Helpers
|
83
39
|
|
40
|
+
- `print_calls(object)` - prints the result to stdout
|
41
|
+
- `write_calls(object, log_file: "file_name")` - writes the result to a file
|
42
|
+
- the default file is `/tmp/tapping_device.log`, but you can change it with `log_file: "new_path"` option
|
84
43
|
|
85
|
-
|
44
|
+
#### Use Cases
|
45
|
+
- Understand a service object/form object's behavior
|
46
|
+
- Debug a messy controller
|
86
47
|
|
87
|
-
|
48
|
+
### Track Traces
|
88
49
|
|
89
|
-
|
90
|
-
def create
|
91
|
-
@manager_params = create_params
|
92
|
-
@manager_params[:first_post_checks] = !is_api?
|
93
|
-
|
94
|
-
manager = NewPostManager.new(current_user, @manager_params)
|
95
|
-
|
96
|
-
print_traces(manager)
|
97
|
-
# .....
|
98
|
-
```
|
50
|
+
By tracking an object's traces, you'll be able to observe the object's journey in your application
|
99
51
|
|
100
|
-
|
52
|
+
![image of print_traces output](https://github.com/st0012/tapping_device/blob/master/images/print_traces.png)
|
101
53
|
|
102
|
-
|
103
|
-
$ rspec spec/requests/posts_controller_spec.rb:603
|
104
|
-
```
|
54
|
+
#### Helpers
|
105
55
|
|
106
|
-
|
56
|
+
- `print_traces(object)` - prints the result to stdout
|
57
|
+
- `write_traces(object, log_file: "file_name")` - writes the result to a file
|
58
|
+
- the default file is `/tmp/tapping_device.log`, but you can change it with `log_file: "new_path"` option
|
107
59
|
|
108
|
-
|
60
|
+
#### Use Cases
|
61
|
+
- Debug argument related issues
|
62
|
+
- Understand how a library uses your objects
|
109
63
|
|
110
|
-
###
|
64
|
+
### Track State Mutations
|
111
65
|
|
112
|
-
|
66
|
+
By tracking an object's traces, you'll be able to observe the state changes happen inside the object between each method call
|
113
67
|
|
114
|
-
|
68
|
+
<img src="https://github.com/st0012/tapping_device/blob/master/images/print_mutations.png" alt="image of print_mutations output" width="50%">
|
115
69
|
|
116
|
-
|
117
|
-
# app/controllers/posts_controller.rb
|
118
|
-
class PostsController
|
119
|
-
def update
|
120
|
-
# ......
|
121
|
-
revisor = PostRevisor.new(post, topic)
|
122
|
-
revisor.revise!(current_user, changes, opts)
|
123
|
-
# ......
|
124
|
-
end
|
125
|
-
end
|
126
|
-
```
|
70
|
+
#### Helpers
|
127
71
|
|
128
|
-
|
72
|
+
- `print_mutations(object)` - prints the result to stdout
|
73
|
+
- `write_mutations(object, log_file: "file_name")` - writes the result to a file
|
74
|
+
- the default file is `/tmp/tapping_device.log`, but you can change it with `log_file: "new_path"` option
|
129
75
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
@editor = editor
|
134
|
-
@fields = fields.with_indifferent_access
|
135
|
-
@opts = opts
|
76
|
+
#### Use Cases
|
77
|
+
- Debug state related issues
|
78
|
+
- Debug memoization issues
|
136
79
|
|
137
|
-
|
138
|
-
|
139
|
-
# ......
|
80
|
+
### Track All Instances Of A Class
|
140
81
|
|
141
|
-
|
142
|
-
@last_version_at = @post.last_version_at || Time.now
|
82
|
+
It's not always easy to directly access the objects we want to track, especially when they're managed by a library (e.g. `ActiveRecord::Relation`). In such cases, you can use these helpers to track the class's instances:
|
143
83
|
|
144
|
-
|
145
|
-
|
84
|
+
- `print_instance_calls(ObjectKlass)`
|
85
|
+
- `print_instance_traces(ObjectKlass)`
|
86
|
+
- `print_instance_mutations(ObjectKlass)`
|
87
|
+
- `write_instance_calls(ObjectKlass)`
|
88
|
+
- `write_instance_traces(ObjectKlass)`
|
89
|
+
- `write_instance_mutations(ObjectKlass)`
|
146
90
|
|
147
|
-
@validate_post = true
|
148
|
-
# ......
|
149
|
-
end
|
150
|
-
```
|
151
91
|
|
152
|
-
|
92
|
+
### Use `with_HELPER_NAME` for chained method calls
|
153
93
|
|
154
|
-
|
94
|
+
In Ruby programs, we often chain multiple methods together like this:
|
155
95
|
|
156
96
|
```ruby
|
157
|
-
|
158
|
-
class PostsController
|
159
|
-
def update
|
160
|
-
# ......
|
161
|
-
revisor = PostRevisor.new(post, topic)
|
162
|
-
print_mutations(revisor)
|
163
|
-
revisor.revise!(current_user, changes, opts)
|
164
|
-
# ......
|
165
|
-
end
|
166
|
-
end
|
97
|
+
SomeService.new(params).perform
|
167
98
|
```
|
168
99
|
|
169
|
-
And
|
100
|
+
And to debug it, we'll need to break the method chain into
|
170
101
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
102
|
+
```ruby
|
103
|
+
service = SomeService.new(params)
|
104
|
+
print_calls(service, options)
|
105
|
+
service.perform
|
106
|
+
```
|
176
107
|
|
177
|
-
|
108
|
+
This kind of code changes are usually annoying, and that's one of the problems I want to solve with `TappingDevice`.
|
178
109
|
|
179
|
-
|
110
|
+
So here's another option, just insert a `with_HELPER_NAME` call in between:
|
180
111
|
|
181
|
-
|
182
|
-
|
183
|
-
|
112
|
+
```ruby
|
113
|
+
SomeService.new(params).with_print_calls(options).perform
|
114
|
+
```
|
184
115
|
|
185
|
-
|
116
|
+
And it'll behave exactly like
|
186
117
|
|
187
118
|
```ruby
|
188
|
-
|
119
|
+
service = SomeService.new(params)
|
120
|
+
print_calls(service, options)
|
121
|
+
service.perform
|
189
122
|
```
|
190
123
|
|
191
|
-
|
192
124
|
## Installation
|
193
125
|
Add this line to your application's Gemfile:
|
194
126
|
|
@@ -296,6 +228,40 @@ The default is `false` because
|
|
296
228
|
2. It's still unclear if this hack safe enough for most applications.
|
297
229
|
|
298
230
|
|
231
|
+
#### `ignore_private`
|
232
|
+
|
233
|
+
Sometimes we use many private methods to perform trivial operations, like
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class Operation
|
237
|
+
def extras
|
238
|
+
dig_attribute("extras")
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def data
|
244
|
+
@data
|
245
|
+
end
|
246
|
+
|
247
|
+
def dig_attribute(attr)
|
248
|
+
data.dig("attributes", attr)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
And we may not be interested in those method calls. If that's the case, you can use the `ignore_private` option
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
operation = Operation.new(params)
|
257
|
+
print_calls(operation, ignore_private: true) #=> only prints the `extras` call
|
258
|
+
```
|
259
|
+
|
260
|
+
#### `only_private`
|
261
|
+
|
262
|
+
This option does the opposite of the `ignore_private` option does.
|
263
|
+
|
264
|
+
|
299
265
|
### Global Configuration
|
300
266
|
|
301
267
|
If you don't want to pass options every time you use a helper, you can use global configuration to change the default values:
|
@@ -341,3 +307,4 @@ The gem is available as open-source under the terms of the [MIT License](https:/
|
|
341
307
|
## Code of Conduct
|
342
308
|
|
343
309
|
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).
|
310
|
+
|
data/lib/tapping_device.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require "active_record"
|
2
1
|
require "active_support/core_ext/module/introspection"
|
3
2
|
require "pry" # for using Method#source
|
4
3
|
|
@@ -34,7 +33,7 @@ class TappingDevice
|
|
34
33
|
def initialize(options = {}, &block)
|
35
34
|
@block = block
|
36
35
|
@output_block = nil
|
37
|
-
@options = process_options(options)
|
36
|
+
@options = process_options(options.dup)
|
38
37
|
@calls = []
|
39
38
|
@disabled = false
|
40
39
|
@with_condition = nil
|
@@ -94,15 +93,19 @@ class TappingDevice
|
|
94
93
|
private
|
95
94
|
|
96
95
|
def build_minimum_trace_point(event_type:)
|
97
|
-
TracePoint.new(event_type) do |tp|
|
96
|
+
TracePoint.new(*event_type) do |tp|
|
98
97
|
next unless filter_condition_satisfied?(tp)
|
99
|
-
next if is_tapping_device_call?(tp)
|
100
98
|
|
101
99
|
filepath, line_number = get_call_location(tp)
|
102
100
|
payload = build_payload(tp: tp, filepath: filepath, line_number: line_number)
|
103
101
|
|
104
|
-
|
105
|
-
|
102
|
+
unless @options[:force_recording]
|
103
|
+
next if is_tapping_device_call?(tp)
|
104
|
+
next if should_be_skipped_by_paths?(filepath)
|
105
|
+
next unless with_condition_satisfied?(payload)
|
106
|
+
next if payload.is_private_call? && @options[:ignore_private]
|
107
|
+
next if !payload.is_private_call? && @options[:only_private]
|
108
|
+
end
|
106
109
|
|
107
110
|
yield(payload)
|
108
111
|
end
|
@@ -137,7 +140,7 @@ class TappingDevice
|
|
137
140
|
end
|
138
141
|
|
139
142
|
def build_payload(tp:, filepath:, line_number:)
|
140
|
-
Payload.
|
143
|
+
Payload.new(
|
141
144
|
target: @target,
|
142
145
|
receiver: tp.self,
|
143
146
|
method_name: tp.callee_id,
|
@@ -148,15 +151,14 @@ class TappingDevice
|
|
148
151
|
line_number: line_number,
|
149
152
|
defined_class: tp.defined_class,
|
150
153
|
trace: get_traces(tp),
|
154
|
+
is_private_call: tp.defined_class.private_method_defined?(tp.callee_id),
|
155
|
+
tag: options[:tag],
|
151
156
|
tp: tp
|
152
|
-
|
157
|
+
)
|
153
158
|
end
|
154
159
|
|
155
160
|
def get_method_object_from(target, method_name)
|
156
|
-
|
157
|
-
rescue ArgumentError
|
158
|
-
method_method = Object.method(:method).unbind
|
159
|
-
method_method.bind(target).call(method_name)
|
161
|
+
Object.instance_method(:method).bind(target).call(method_name)
|
160
162
|
rescue NameError
|
161
163
|
# if any part of the program uses Refinement to extend its methods
|
162
164
|
# we might still get NoMethodError when trying to get that method outside the scope
|
@@ -204,6 +206,10 @@ class TappingDevice
|
|
204
206
|
options[:event_type] ||= config[:event_type]
|
205
207
|
options[:hijack_attr_methods] ||= config[:hijack_attr_methods]
|
206
208
|
options[:track_as_records] ||= config[:track_as_records]
|
209
|
+
options[:ignore_private] ||= config[:ignore_private]
|
210
|
+
options[:only_private] ||= config[:only_private]
|
211
|
+
# for debugging the gem more easily
|
212
|
+
options[:force_recording] ||= false
|
207
213
|
|
208
214
|
options[:descendants] ||= []
|
209
215
|
options[:root_device] ||= self
|
@@ -20,10 +20,14 @@ class TappingDevice
|
|
20
20
|
|
21
21
|
def is_writer_method?(method_name)
|
22
22
|
has_definition_source?(method_name) && method_name.match?(/\w+=/) && target.method(method_name).source.match?(/attr_writer|attr_accessor/)
|
23
|
+
rescue MethodSource::SourceNotFoundError
|
24
|
+
false
|
23
25
|
end
|
24
26
|
|
25
27
|
def is_reader_method?(method_name)
|
26
28
|
has_definition_source?(method_name) && target.method(method_name).source.match?(/attr_reader|attr_accessor/)
|
29
|
+
rescue MethodSource::SourceNotFoundError
|
30
|
+
false
|
27
31
|
end
|
28
32
|
|
29
33
|
def has_definition_source?(method_name)
|
@@ -1,7 +1,6 @@
|
|
1
|
-
require "
|
1
|
+
require "logger"
|
2
|
+
require "tapping_device/output/payload_wrapper"
|
2
3
|
require "tapping_device/output/writer"
|
3
|
-
require "tapping_device/output/stdout_writer"
|
4
|
-
require "tapping_device/output/file_writer"
|
5
4
|
|
6
5
|
class TappingDevice
|
7
6
|
module Output
|
@@ -13,16 +12,16 @@ class TappingDevice
|
|
13
12
|
|
14
13
|
module Helpers
|
15
14
|
def and_write(payload_method = nil, options: {}, &block)
|
16
|
-
and_output(payload_method, options: options,
|
15
|
+
and_output(payload_method, options: options, logger: Logger.new(options[:log_file]), &block)
|
17
16
|
end
|
18
17
|
|
19
18
|
def and_print(payload_method = nil, options: {}, &block)
|
20
|
-
and_output(payload_method, options: options,
|
19
|
+
and_output(payload_method, options: options, logger: Logger.new($stdout), &block)
|
21
20
|
end
|
22
21
|
|
23
|
-
def and_output(payload_method = nil, options: {},
|
22
|
+
def and_output(payload_method = nil, options: {}, logger:, &block)
|
24
23
|
output_block = generate_output_block(payload_method, block)
|
25
|
-
@output_writer =
|
24
|
+
@output_writer = Writer.new(options: options, output_block: output_block, logger: logger)
|
26
25
|
self
|
27
26
|
end
|
28
27
|
|
@@ -1,13 +1,44 @@
|
|
1
|
+
require "pastel"
|
2
|
+
|
1
3
|
class TappingDevice
|
2
4
|
module Output
|
3
|
-
class
|
5
|
+
class PayloadWrapper
|
4
6
|
UNDEFINED = "[undefined]"
|
7
|
+
PRIVATE_MARK = " (private)"
|
8
|
+
|
9
|
+
PASTEL = Pastel.new
|
10
|
+
PASTEL.alias_color(:orange, :bright_red, :bright_yellow)
|
11
|
+
|
12
|
+
TappingDevice::Payload::ATTRS.each do |attr|
|
13
|
+
define_method attr do |options = {}|
|
14
|
+
@payload.send(attr)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
alias :is_private_call? :is_private_call
|
19
|
+
|
20
|
+
def method_head
|
21
|
+
@payload.method_head
|
22
|
+
end
|
23
|
+
|
24
|
+
def location(options = {})
|
25
|
+
@payload.location(options)
|
26
|
+
end
|
5
27
|
|
6
28
|
alias :raw_arguments :arguments
|
7
29
|
alias :raw_return_value :return_value
|
8
30
|
|
31
|
+
def initialize(payload)
|
32
|
+
@payload = payload
|
33
|
+
end
|
34
|
+
|
9
35
|
def method_name(options = {})
|
10
|
-
":#{
|
36
|
+
name = ":#{@payload.method_name}"
|
37
|
+
|
38
|
+
name += " [#{tag}]" if tag
|
39
|
+
name += PRIVATE_MARK if is_private_call?
|
40
|
+
|
41
|
+
name
|
11
42
|
end
|
12
43
|
|
13
44
|
def arguments(options = {})
|
@@ -18,29 +49,13 @@ class TappingDevice
|
|
18
49
|
generate_string_result(raw_return_value, options[:inspect])
|
19
50
|
end
|
20
51
|
|
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
52
|
PAYLOAD_ATTRIBUTES = {
|
38
|
-
method_name: {symbol: "", color:
|
39
|
-
location: {symbol: "from:", color:
|
40
|
-
return_value: {symbol: "=>", color:
|
41
|
-
arguments: {symbol: "<=", color:
|
42
|
-
ivar_changes: {symbol: "changes:\n", color:
|
43
|
-
defined_class: {symbol: "#", color:
|
53
|
+
method_name: {symbol: "", color: :bright_blue},
|
54
|
+
location: {symbol: "from:", color: :green},
|
55
|
+
return_value: {symbol: "=>", color: :magenta},
|
56
|
+
arguments: {symbol: "<=", color: :orange},
|
57
|
+
ivar_changes: {symbol: "changes:\n", color: :blue},
|
58
|
+
defined_class: {symbol: "#", color: :yellow}
|
44
59
|
}
|
45
60
|
|
46
61
|
PAYLOAD_ATTRIBUTES.each do |attribute, attribute_options|
|
@@ -53,7 +68,7 @@ class TappingDevice
|
|
53
68
|
call_result = send("original_#{attribute}", options)
|
54
69
|
|
55
70
|
if options[:colorize]
|
56
|
-
|
71
|
+
PASTEL.send(color, call_result)
|
57
72
|
else
|
58
73
|
call_result
|
59
74
|
end
|
@@ -83,7 +98,7 @@ class TappingDevice
|
|
83
98
|
return unless arg_name
|
84
99
|
|
85
100
|
arg_name = ":#{arg_name}"
|
86
|
-
arg_name =
|
101
|
+
arg_name = PASTEL.orange(arg_name) if options[:colorize]
|
87
102
|
msg = "Passed as #{arg_name} in '#{defined_class(options)}##{method_name(options)}' at #{location(options)}\n"
|
88
103
|
msg += " > #{method_head}\n" if with_method_head
|
89
104
|
msg
|
@@ -100,17 +115,17 @@ class TappingDevice
|
|
100
115
|
end
|
101
116
|
|
102
117
|
def ivar_changes(options = {})
|
103
|
-
|
118
|
+
@payload.ivar_changes.map do |ivar, value_changes|
|
104
119
|
before = generate_string_result(value_changes[:before], options[:inspect])
|
105
120
|
after = generate_string_result(value_changes[:after], options[:inspect])
|
106
121
|
|
107
122
|
if options[:colorize]
|
108
|
-
ivar =
|
109
|
-
before =
|
110
|
-
after =
|
123
|
+
ivar = PASTEL.orange(ivar)
|
124
|
+
before = PASTEL.bright_blue(before.to_s)
|
125
|
+
after = PASTEL.bright_blue(after.to_s)
|
111
126
|
end
|
112
127
|
|
113
|
-
" #{ivar}: #{before
|
128
|
+
" #{ivar}: #{before} => #{after}"
|
114
129
|
end.join("\n")
|
115
130
|
end
|
116
131
|
|
@@ -126,10 +141,6 @@ class TappingDevice
|
|
126
141
|
|
127
142
|
private
|
128
143
|
|
129
|
-
def value_with_color(value, color)
|
130
|
-
"#{COLORS[color]}#{value}#{COLORS[:reset]}"
|
131
|
-
end
|
132
|
-
|
133
144
|
def generate_string_result(obj, inspect)
|
134
145
|
case obj
|
135
146
|
when Array
|
@@ -1,19 +1,21 @@
|
|
1
1
|
class TappingDevice
|
2
2
|
module Output
|
3
3
|
class Writer
|
4
|
-
def initialize(options
|
4
|
+
def initialize(options:, output_block:, logger:)
|
5
5
|
@options = options
|
6
6
|
@output_block = output_block
|
7
|
+
@logger = logger
|
7
8
|
end
|
8
9
|
|
9
10
|
def write!(payload)
|
10
|
-
|
11
|
+
output = generate_output(payload)
|
12
|
+
@logger << output
|
11
13
|
end
|
12
14
|
|
13
15
|
private
|
14
16
|
|
15
17
|
def generate_output(payload)
|
16
|
-
@output_block.call(
|
18
|
+
@output_block.call(PayloadWrapper.new(payload), @options)
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -1,22 +1,32 @@
|
|
1
1
|
class TappingDevice
|
2
|
-
class Payload
|
2
|
+
class Payload
|
3
3
|
ATTRS = [
|
4
4
|
:target, :receiver, :method_name, :method_object, :arguments, :return_value, :filepath, :line_number,
|
5
|
-
:defined_class, :trace, :tp, :ivar_changes
|
5
|
+
:defined_class, :trace, :tag, :tp, :ivar_changes, :is_private_call
|
6
6
|
]
|
7
7
|
|
8
|
-
ATTRS
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
end
|
8
|
+
attr_accessor(*ATTRS)
|
9
|
+
|
10
|
+
alias :is_private_call? :is_private_call
|
13
11
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
def initialize(
|
13
|
+
target:, receiver:, method_name:, method_object:, arguments:, return_value:, filepath:, line_number:,
|
14
|
+
defined_class:, trace:, tag:, tp:, is_private_call:
|
15
|
+
)
|
16
|
+
@target = target
|
17
|
+
@receiver = receiver
|
18
|
+
@method_name = method_name
|
19
|
+
@method_object = method_object
|
20
|
+
@arguments = arguments
|
21
|
+
@return_value = return_value
|
22
|
+
@filepath = filepath
|
23
|
+
@line_number = line_number
|
24
|
+
@defined_class = defined_class
|
25
|
+
@trace = trace
|
26
|
+
@tag = tag
|
27
|
+
@tp = tp
|
28
|
+
@ivar_changes = {}
|
29
|
+
@is_private_call = is_private_call
|
20
30
|
end
|
21
31
|
|
22
32
|
def method_head
|
@@ -20,28 +20,29 @@ class TappingDevice
|
|
20
20
|
TappingDevice::Trackers::MutationTracker.new(options, &block).track(object)
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
[:calls, :traces, :mutations].each do |subject|
|
24
|
+
[:print, :write].each do |output_action|
|
25
|
+
helper_method_name = "#{output_action}_#{subject}"
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
define_method helper_method_name do |target, options = {}|
|
28
|
+
send("output_#{subject}", target, options, output_action: "and_#{output_action}")
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
define_method "with_#{helper_method_name}" do |options = {}|
|
32
|
+
send(helper_method_name, self, options)
|
33
|
+
self
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
end
|
36
|
+
define_method "#{output_action}_instance_#{subject}" do |target_klass, options = {}|
|
37
|
+
collection_proxy = AsyncCollectionProxy.new
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
tap_init!(target_klass, options.merge(force_recording: true)) do |payload|
|
40
|
+
collection_proxy << send(helper_method_name, payload.return_value, options)
|
41
|
+
end
|
42
42
|
|
43
|
-
|
44
|
-
|
43
|
+
collection_proxy
|
44
|
+
end
|
45
|
+
end
|
45
46
|
end
|
46
47
|
|
47
48
|
private
|
@@ -84,12 +85,15 @@ class TappingDevice
|
|
84
85
|
[options, output_options]
|
85
86
|
end
|
86
87
|
|
88
|
+
# CollectionProxy delegates chained actions to multiple devices
|
87
89
|
class CollectionProxy
|
90
|
+
CHAINABLE_ACTIONS = [:stop!, :stop_when, :with]
|
91
|
+
|
88
92
|
def initialize(devices)
|
89
93
|
@devices = devices
|
90
94
|
end
|
91
95
|
|
92
|
-
|
96
|
+
CHAINABLE_ACTIONS.each do |method|
|
93
97
|
define_method method do |&block|
|
94
98
|
@devices.each do |device|
|
95
99
|
device.send(method, &block)
|
@@ -97,6 +101,32 @@ class TappingDevice
|
|
97
101
|
end
|
98
102
|
end
|
99
103
|
end
|
104
|
+
|
105
|
+
# AsyncCollectionProxy delegates chained actions to multiple device "asyncronously"
|
106
|
+
# when we use tapping methods like `tap_init!` to create sub-devices
|
107
|
+
# we need to find a way to pass the chained actions to every sub-device that's created
|
108
|
+
# and this can only happen asyncronously as we won't know when'll that happen
|
109
|
+
class AsyncCollectionProxy < CollectionProxy
|
110
|
+
def initialize(devices = [])
|
111
|
+
super
|
112
|
+
@blocks = {}
|
113
|
+
end
|
114
|
+
|
115
|
+
CHAINABLE_ACTIONS.each do |method|
|
116
|
+
define_method method do |&block|
|
117
|
+
super(&block)
|
118
|
+
@blocks[method] = block
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def <<(device)
|
123
|
+
@devices << device
|
124
|
+
|
125
|
+
@blocks.each do |method, block|
|
126
|
+
device.send(method, &block)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
100
130
|
end
|
101
131
|
end
|
102
132
|
|
@@ -1,10 +1,27 @@
|
|
1
1
|
class TappingDevice
|
2
2
|
module Trackers
|
3
3
|
class InitializationTracker < TappingDevice
|
4
|
+
def initialize(options = {}, &block)
|
5
|
+
super
|
6
|
+
event_type = @options[:event_type]
|
7
|
+
# if a class doesn't override the 'initialize' method
|
8
|
+
# Class.new will only trigger c_return or c_call
|
9
|
+
@options[:event_type] = [event_type, "c_#{event_type}"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def track(object)
|
13
|
+
super
|
14
|
+
@is_active_record_model = defined?(ActiveRecord) && target.ancestors.include?(ActiveRecord::Base)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
4
18
|
def build_payload(tp:, filepath:, line_number:)
|
5
19
|
payload = super
|
6
|
-
|
7
|
-
payload
|
20
|
+
|
21
|
+
return payload if @is_active_record_model
|
22
|
+
|
23
|
+
payload.return_value = payload.receiver
|
24
|
+
payload.receiver = target
|
8
25
|
payload
|
9
26
|
end
|
10
27
|
|
@@ -16,8 +33,17 @@ class TappingDevice
|
|
16
33
|
receiver = tp.self
|
17
34
|
method_name = tp.callee_id
|
18
35
|
|
19
|
-
if
|
20
|
-
|
36
|
+
if @is_active_record_model
|
37
|
+
# ActiveRecord redefines model classes' .new method,
|
38
|
+
# so instead of calling Model#initialize, it'll actually call Model.new
|
39
|
+
# see https://github.com/rails/rails/blob/master/activerecord/lib/active_record/inheritance.rb#L50
|
40
|
+
method_name == :new &&
|
41
|
+
receiver.is_a?(Class) &&
|
42
|
+
# this checks if the model class is the target class or a subclass of it
|
43
|
+
receiver.ancestors.include?(target) &&
|
44
|
+
# Model.new triggers both c_return and return events. so we should only return in 1 type of the events
|
45
|
+
# otherwise the callback will be triggered twice
|
46
|
+
tp.event == :return
|
21
47
|
else
|
22
48
|
method_name == :initialize && receiver.is_a?(target)
|
23
49
|
end
|
@@ -47,7 +47,7 @@ class TappingDevice
|
|
47
47
|
payload = super
|
48
48
|
|
49
49
|
if change_capturing_event?(tp)
|
50
|
-
payload
|
50
|
+
payload.ivar_changes = capture_ivar_changes
|
51
51
|
end
|
52
52
|
|
53
53
|
payload
|
@@ -55,15 +55,14 @@ class TappingDevice
|
|
55
55
|
|
56
56
|
def capture_ivar_changes
|
57
57
|
changes = {}
|
58
|
-
|
59
58
|
additional_keys = @latest_instance_variables.keys - @instance_variables_snapshot.keys
|
60
59
|
additional_keys.each do |key|
|
61
|
-
changes[key] = {before: Output::
|
60
|
+
changes[key] = {before: Output::PayloadWrapper::UNDEFINED, after: @latest_instance_variables[key]}
|
62
61
|
end
|
63
62
|
|
64
63
|
removed_keys = @instance_variables_snapshot.keys - @latest_instance_variables.keys
|
65
64
|
removed_keys.each do |key|
|
66
|
-
changes[key] = {before: @instance_variables_snapshot[key], after: Output::
|
65
|
+
changes[key] = {before: @instance_variables_snapshot[key], after: Output::PayloadWrapper::UNDEFINED}
|
67
66
|
end
|
68
67
|
|
69
68
|
remained_keys = @latest_instance_variables.keys - additional_keys
|
data/tapping_device.gemspec
CHANGED
@@ -26,16 +26,10 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ["lib"]
|
28
28
|
|
29
|
-
if ENV["RAILS_VERSION"]
|
30
|
-
spec.add_dependency "activerecord", "~> #{ENV["RAILS_VERSION"]}"
|
31
|
-
else
|
32
|
-
spec.add_dependency "activerecord", ">= 5.2"
|
33
|
-
end
|
34
|
-
|
35
29
|
spec.add_dependency "pry" # for using Method#source in MutationTracker
|
36
30
|
spec.add_dependency "activesupport"
|
31
|
+
spec.add_dependency "pastel"
|
37
32
|
|
38
|
-
spec.add_development_dependency "sqlite3", ">= 1.3.6"
|
39
33
|
spec.add_development_dependency "database_cleaner"
|
40
34
|
spec.add_development_dependency "bundler", "~> 2.0"
|
41
35
|
spec.add_development_dependency "rake", "~> 13.0"
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapping_device
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- st0012
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: activerecord
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '5.2'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '5.2'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: pry
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,19 +39,19 @@ dependencies:
|
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: pastel
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - ">="
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
62
|
-
type: :
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
52
|
- - ">="
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
54
|
+
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: database_cleaner
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -154,8 +140,8 @@ files:
|
|
154
140
|
- CHANGELOG.md
|
155
141
|
- CODE_OF_CONDUCT.md
|
156
142
|
- Gemfile
|
157
|
-
- Gemfile.lock
|
158
143
|
- LICENSE.txt
|
144
|
+
- Makefile
|
159
145
|
- README.md
|
160
146
|
- Rakefile
|
161
147
|
- bin/console
|
@@ -170,9 +156,7 @@ files:
|
|
170
156
|
- lib/tapping_device/manageable.rb
|
171
157
|
- lib/tapping_device/method_hijacker.rb
|
172
158
|
- lib/tapping_device/output.rb
|
173
|
-
- lib/tapping_device/output/
|
174
|
-
- lib/tapping_device/output/payload.rb
|
175
|
-
- lib/tapping_device/output/stdout_writer.rb
|
159
|
+
- lib/tapping_device/output/payload_wrapper.rb
|
176
160
|
- lib/tapping_device/output/writer.rb
|
177
161
|
- lib/tapping_device/payload.rb
|
178
162
|
- lib/tapping_device/trackable.rb
|
@@ -205,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
189
|
- !ruby/object:Gem::Version
|
206
190
|
version: '0'
|
207
191
|
requirements: []
|
208
|
-
rubygems_version: 3.
|
192
|
+
rubygems_version: 3.2.15
|
209
193
|
signing_key:
|
210
194
|
specification_version: 4
|
211
195
|
summary: tapping_device lets you understand what your Ruby objects do without digging
|
data/Gemfile.lock
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
tapping_device (0.5.3)
|
5
|
-
activerecord (>= 5.2)
|
6
|
-
activesupport
|
7
|
-
pry
|
8
|
-
|
9
|
-
GEM
|
10
|
-
remote: https://rubygems.org/
|
11
|
-
specs:
|
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)
|
18
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
|
-
i18n (>= 0.7, < 2)
|
20
|
-
minitest (~> 5.1)
|
21
|
-
tzinfo (~> 1.1)
|
22
|
-
zeitwerk (~> 2.2, >= 2.2.2)
|
23
|
-
coderay (1.1.3)
|
24
|
-
concurrent-ruby (1.1.6)
|
25
|
-
database_cleaner (1.7.0)
|
26
|
-
diff-lcs (1.3)
|
27
|
-
docile (1.3.2)
|
28
|
-
i18n (1.8.3)
|
29
|
-
concurrent-ruby (~> 1.0)
|
30
|
-
json (2.3.0)
|
31
|
-
method_source (1.0.0)
|
32
|
-
minitest (5.14.1)
|
33
|
-
pry (0.13.1)
|
34
|
-
coderay (~> 1.1)
|
35
|
-
method_source (~> 1.0)
|
36
|
-
rake (13.0.1)
|
37
|
-
rspec (3.8.0)
|
38
|
-
rspec-core (~> 3.8.0)
|
39
|
-
rspec-expectations (~> 3.8.0)
|
40
|
-
rspec-mocks (~> 3.8.0)
|
41
|
-
rspec-core (3.8.2)
|
42
|
-
rspec-support (~> 3.8.0)
|
43
|
-
rspec-expectations (3.8.4)
|
44
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
45
|
-
rspec-support (~> 3.8.0)
|
46
|
-
rspec-mocks (3.8.1)
|
47
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
-
rspec-support (~> 3.8.0)
|
49
|
-
rspec-support (3.8.2)
|
50
|
-
simplecov (0.17.1)
|
51
|
-
docile (~> 1.1)
|
52
|
-
json (>= 1.8, < 3)
|
53
|
-
simplecov-html (~> 0.10.0)
|
54
|
-
simplecov-html (0.10.2)
|
55
|
-
sqlite3 (1.4.1)
|
56
|
-
thread_safe (0.3.6)
|
57
|
-
tzinfo (1.2.7)
|
58
|
-
thread_safe (~> 0.1)
|
59
|
-
zeitwerk (2.3.0)
|
60
|
-
|
61
|
-
PLATFORMS
|
62
|
-
ruby
|
63
|
-
|
64
|
-
DEPENDENCIES
|
65
|
-
bundler (~> 2.0)
|
66
|
-
database_cleaner
|
67
|
-
rake (~> 13.0)
|
68
|
-
rspec (~> 3.0)
|
69
|
-
simplecov (= 0.17.1)
|
70
|
-
sqlite3 (>= 1.3.6)
|
71
|
-
tapping_device!
|
72
|
-
|
73
|
-
BUNDLED WITH
|
74
|
-
2.1.1
|
@@ -1,21 +0,0 @@
|
|
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
|