tapping_device 0.5.2 → 0.5.7
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 +4 -4
- data/.DS_Store +0 -0
- data/CHANGELOG.md +210 -0
- data/Gemfile.lock +16 -11
- data/README.md +178 -127
- data/lib/tapping_device.rb +45 -31
- data/lib/tapping_device/configurable.rb +27 -0
- data/lib/tapping_device/method_hijacker.rb +51 -0
- data/lib/tapping_device/output.rb +42 -0
- data/lib/tapping_device/output/file_writer.rb +21 -0
- data/lib/tapping_device/output/payload.rb +166 -0
- data/lib/tapping_device/output/stdout_writer.rb +9 -0
- data/lib/tapping_device/output/writer.rb +20 -0
- data/lib/tapping_device/payload.rb +2 -3
- data/lib/tapping_device/trackable.rb +78 -19
- data/lib/tapping_device/trackers/initialization_tracker.rb +28 -2
- data/lib/tapping_device/trackers/mutation_tracker.rb +3 -23
- data/lib/tapping_device/version.rb +1 -1
- data/tapping_device.gemspec +2 -0
- metadata +38 -3
- data/lib/tapping_device/output_payload.rb +0 -173
@@ -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
|
@@ -2,7 +2,7 @@ class TappingDevice
|
|
2
2
|
class Payload < Hash
|
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
8
|
ATTRS.each do |attr|
|
@@ -20,8 +20,7 @@ class TappingDevice
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def method_head
|
23
|
-
|
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 = {})
|
@@ -20,47 +20,80 @@ 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
|
-
|
30
|
-
|
31
|
-
|
27
|
+
define_method helper_method_name do |target, options = {}|
|
28
|
+
send("output_#{subject}", target, options, output_action: "and_#{output_action}")
|
29
|
+
end
|
30
|
+
|
31
|
+
define_method "with_#{helper_method_name}" do |options = {}|
|
32
|
+
send(helper_method_name, self, options)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
define_method "#{output_action}_instance_#{subject}" do |target_klass, options = {}|
|
37
|
+
collection_proxy = AsyncCollectionProxy.new
|
38
|
+
|
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
|
+
|
43
|
+
collection_proxy
|
44
|
+
end
|
32
45
|
end
|
33
|
-
CollectionProxy.new([device_1, device_2])
|
34
46
|
end
|
35
47
|
|
36
|
-
|
37
|
-
|
48
|
+
private
|
49
|
+
|
50
|
+
def output_calls(target, options = {}, output_action:)
|
51
|
+
device_options, output_options = separate_options(options)
|
38
52
|
|
39
|
-
tap_on!(target,
|
53
|
+
tap_on!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
|
40
54
|
output_payload.detail_call_info(output_options)
|
41
55
|
end
|
42
56
|
end
|
43
57
|
|
44
|
-
def
|
45
|
-
output_options =
|
58
|
+
def output_traces(target, options = {}, output_action:)
|
59
|
+
device_options, output_options = separate_options(options)
|
60
|
+
device_options[:event_type] = :call
|
61
|
+
|
62
|
+
device_1 = tap_on!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
|
63
|
+
"Called #{output_payload.method_name_and_location(output_options)}\n"
|
64
|
+
end
|
65
|
+
device_2 = tap_passed!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
|
66
|
+
output_payload.passed_at(output_options)
|
67
|
+
end
|
68
|
+
CollectionProxy.new([device_1, device_2])
|
69
|
+
end
|
70
|
+
|
71
|
+
def output_mutations(target, options = {}, output_action:)
|
72
|
+
device_options, output_options = separate_options(options)
|
46
73
|
|
47
|
-
tap_mutation!(target,
|
74
|
+
tap_mutation!(target, device_options).send(output_action, options: output_options) do |output_payload, output_options|
|
48
75
|
output_payload.call_info_with_ivar_changes(output_options)
|
49
76
|
end
|
50
77
|
end
|
51
78
|
|
52
|
-
|
79
|
+
def separate_options(options)
|
80
|
+
output_options = Output::DEFAULT_OPTIONS.keys.each_with_object({}) do |key, hash|
|
81
|
+
hash[key] = options.fetch(key, TappingDevice.config[key])
|
82
|
+
options.delete(key)
|
83
|
+
end
|
53
84
|
|
54
|
-
|
55
|
-
{inspect: options.delete(:inspect), colorize: options.fetch(:colorize, true)}
|
85
|
+
[options, output_options]
|
56
86
|
end
|
57
87
|
|
88
|
+
# CollectionProxy delegates chained actions to multiple devices
|
58
89
|
class CollectionProxy
|
90
|
+
CHAINABLE_ACTIONS = [:stop!, :stop_when, :with]
|
91
|
+
|
59
92
|
def initialize(devices)
|
60
93
|
@devices = devices
|
61
94
|
end
|
62
95
|
|
63
|
-
|
96
|
+
CHAINABLE_ACTIONS.each do |method|
|
64
97
|
define_method method do |&block|
|
65
98
|
@devices.each do |device|
|
66
99
|
device.send(method, &block)
|
@@ -68,6 +101,32 @@ class TappingDevice
|
|
68
101
|
end
|
69
102
|
end
|
70
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
|
71
130
|
end
|
72
131
|
end
|
73
132
|
|
@@ -1,8 +1,25 @@
|
|
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 = target.ancestors.include?(ActiveRecord::Base)
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
4
18
|
def build_payload(tp:, filepath:, line_number:)
|
5
19
|
payload = super
|
20
|
+
|
21
|
+
return payload if @is_active_record_model
|
22
|
+
|
6
23
|
payload[:return_value] = payload[:receiver]
|
7
24
|
payload[:receiver] = target
|
8
25
|
payload
|
@@ -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
|
@@ -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:
|
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:
|
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
|
data/tapping_device.gemspec
CHANGED
@@ -33,6 +33,8 @@ 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"
|
37
|
+
spec.add_dependency "pastel"
|
36
38
|
|
37
39
|
spec.add_development_dependency "sqlite3", ">= 1.3.6"
|
38
40
|
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.
|
4
|
+
version: 0.5.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- st0012
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -38,6 +38,34 @@ 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'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pastel
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: sqlite3
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,6 +165,7 @@ files:
|
|
137
165
|
- ".rspec"
|
138
166
|
- ".ruby-version"
|
139
167
|
- ".travis.yml"
|
168
|
+
- CHANGELOG.md
|
140
169
|
- CODE_OF_CONDUCT.md
|
141
170
|
- Gemfile
|
142
171
|
- Gemfile.lock
|
@@ -150,9 +179,15 @@ files:
|
|
150
179
|
- images/print_mutations.png
|
151
180
|
- images/print_traces.png
|
152
181
|
- lib/tapping_device.rb
|
182
|
+
- lib/tapping_device/configurable.rb
|
153
183
|
- lib/tapping_device/exceptions.rb
|
154
184
|
- lib/tapping_device/manageable.rb
|
155
|
-
- lib/tapping_device/
|
185
|
+
- lib/tapping_device/method_hijacker.rb
|
186
|
+
- lib/tapping_device/output.rb
|
187
|
+
- lib/tapping_device/output/file_writer.rb
|
188
|
+
- lib/tapping_device/output/payload.rb
|
189
|
+
- lib/tapping_device/output/stdout_writer.rb
|
190
|
+
- lib/tapping_device/output/writer.rb
|
156
191
|
- lib/tapping_device/payload.rb
|
157
192
|
- lib/tapping_device/trackable.rb
|
158
193
|
- 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
|