rshade 0.1.9.1 → 0.2.1
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/.gitignore +2 -1
- data/.rubocop.yml +31 -0
- data/Gemfile +9 -2
- data/Gemfile.lock +11 -2
- data/README.md +20 -3
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/lib/rshade/config/registry.rb +40 -0
- data/lib/rshade/config/stack_store.rb +63 -0
- data/lib/rshade/config/store.rb +12 -7
- data/lib/rshade/config.rb +8 -7
- data/lib/rshade/core_extensions/object/reveal.rb +37 -5
- data/lib/rshade/event.rb +15 -5
- data/lib/rshade/event_observer.rb +3 -1
- data/lib/rshade/event_processor.rb +12 -11
- data/lib/rshade/event_tree.rb +18 -18
- data/lib/rshade/filter/abstract_filter.rb +3 -1
- data/lib/rshade/filter/default.rb +4 -2
- data/lib/rshade/filter/exclude_path_filter.rb +5 -3
- data/lib/rshade/filter/filter_builder.rb +14 -3
- data/lib/rshade/filter/filter_composition.rb +29 -25
- data/lib/rshade/filter/include_path_filter.rb +6 -1
- data/lib/rshade/filter/variable_filter.rb +5 -1
- data/lib/rshade/formatter/stack/json.rb +51 -0
- data/lib/rshade/formatter/stack/stdout.rb +13 -0
- data/lib/rshade/formatter/stack/string.rb +53 -0
- data/lib/rshade/formatter/trace/file.rb +31 -0
- data/lib/rshade/formatter/trace/html.rb +36 -0
- data/lib/rshade/formatter/trace/json.rb +60 -0
- data/lib/rshade/formatter/trace/stdout.rb +14 -0
- data/lib/rshade/formatter/trace/string.rb +48 -0
- data/lib/rshade/rspec/rspec.rb +8 -5
- data/lib/rshade/serializer/traversal.rb +10 -8
- data/lib/rshade/stack.rb +26 -0
- data/lib/rshade/stack_frame.rb +61 -0
- data/lib/rshade/trace.rb +7 -4
- data/lib/rshade/trace_observable.rb +10 -5
- data/lib/rshade/version.rb +3 -1
- data/lib/rshade.rb +25 -8
- data/rshade.gemspec +24 -22
- metadata +29 -35
- data/lib/rshade/formatter/file.rb +0 -28
- data/lib/rshade/formatter/html.rb +0 -33
- data/lib/rshade/formatter/json.rb +0 -59
- data/lib/rshade/formatter/stdout.rb +0 -10
- data/lib/rshade/formatter/string.rb +0 -36
- data/lib/rshade/rails/rails.rb +0 -0
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RShade
|
2
4
|
module Filter
|
3
5
|
class ExcludePathFilter < IncludePathFilter
|
@@ -13,13 +15,13 @@ module RShade
|
|
13
15
|
|
14
16
|
def call(event)
|
15
17
|
event_path = event.path
|
16
|
-
|
18
|
+
paths.none? do |path|
|
17
19
|
next str?(path, event_path) if path.is_a? String
|
18
20
|
next regexp?(path, event_path) if path.is_a? Regexp
|
21
|
+
|
19
22
|
false
|
20
23
|
end
|
21
24
|
end
|
22
|
-
|
23
25
|
end
|
24
26
|
end
|
25
|
-
end
|
27
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RShade
|
2
4
|
module Filter
|
3
5
|
class FilterBuilder
|
@@ -18,11 +20,20 @@ module RShade
|
|
18
20
|
arg1 = arr[1]
|
19
21
|
arg2 = nil
|
20
22
|
arg2 = arr[2] if arity == 2
|
21
|
-
arg1 =
|
22
|
-
|
23
|
+
arg1 = if arg1.is_a?(Array)
|
24
|
+
traverse(arg1)
|
25
|
+
else
|
26
|
+
RShade::Filter::FilterComposition.new(arg1)
|
27
|
+
end
|
28
|
+
|
29
|
+
arg2 = if arg2.is_a?(Array)
|
30
|
+
traverse(arg2)
|
31
|
+
else
|
32
|
+
RShade::Filter::FilterComposition.new(arg2)
|
33
|
+
end
|
23
34
|
|
24
35
|
RShade::Filter::FilterComposition.new(op, arg1, arg2)
|
25
36
|
end
|
26
37
|
end
|
27
38
|
end
|
28
|
-
end
|
39
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RShade
|
2
4
|
module Filter
|
3
5
|
class FilterComposition
|
@@ -5,53 +7,55 @@ module RShade
|
|
5
7
|
AND_OP = :and
|
6
8
|
OR_OP = :or
|
7
9
|
UNARY_OP = :unary
|
8
|
-
attr_reader :
|
10
|
+
attr_reader :value, :left, :right
|
9
11
|
attr_accessor :parent
|
10
12
|
|
11
13
|
# @param [#call, Enumerable] left
|
12
14
|
# @param [#call, Enumerable] right
|
13
|
-
def initialize(
|
14
|
-
@
|
15
|
+
def initialize(value, left = nil, right = nil)
|
16
|
+
@value = value
|
15
17
|
@left = left
|
16
18
|
@right = right
|
17
19
|
end
|
18
20
|
|
21
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
19
22
|
def call(event)
|
20
|
-
case
|
23
|
+
case value
|
21
24
|
when UNARY_OP
|
22
|
-
|
25
|
+
left&.call(event)
|
23
26
|
when AND_OP
|
24
|
-
|
27
|
+
left&.call(event) && right&.call(event)
|
25
28
|
when OR_OP
|
26
|
-
|
27
|
-
r = right&.call(event)
|
28
|
-
# puts "#{left} => #{l} OR #{right} => #{r}"
|
29
|
-
return l || r
|
29
|
+
left&.call(event) || right&.call(event)
|
30
30
|
else
|
31
|
-
|
31
|
+
value.call(event)
|
32
32
|
end
|
33
33
|
end
|
34
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
34
35
|
|
35
36
|
def each(&block)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
37
|
+
yield value unless left && right
|
38
|
+
left&.each(&block)
|
39
|
+
right&.each(&block)
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
yield right
|
42
|
+
def config_filter(type, &block)
|
43
|
+
filter = find do |f|
|
44
|
+
f.is_a? type
|
46
45
|
end
|
46
|
+
filter&.config(&block)
|
47
47
|
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
# for debug purposes, show each filter and result of evaluation
|
50
|
+
def filter_results(event)
|
51
|
+
each_with_object([]) do |filter, arr|
|
52
|
+
arr << [filter, filter.call(event)]
|
52
53
|
end
|
53
|
-
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.build(arr)
|
57
|
+
::RShade::Filter::FilterBuilder.build(arr)
|
54
58
|
end
|
55
59
|
end
|
56
60
|
end
|
57
|
-
end
|
61
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RShade
|
2
4
|
module Filter
|
3
5
|
class IncludePathFilter < AbstractFilter
|
@@ -6,6 +8,7 @@ module RShade
|
|
6
8
|
NAME = :include_paths
|
7
9
|
|
8
10
|
def initialize
|
11
|
+
super
|
9
12
|
@paths = []
|
10
13
|
end
|
11
14
|
|
@@ -22,6 +25,7 @@ module RShade
|
|
22
25
|
paths.any? do |path|
|
23
26
|
next str?(path, event_path) if path.is_a? String
|
24
27
|
next regexp?(path, event_path) if path.is_a? Regexp
|
28
|
+
|
25
29
|
false
|
26
30
|
end
|
27
31
|
end
|
@@ -31,6 +35,7 @@ module RShade
|
|
31
35
|
end
|
32
36
|
|
33
37
|
private
|
38
|
+
|
34
39
|
def str?(str, event_path)
|
35
40
|
event_path.include?(str)
|
36
41
|
end
|
@@ -40,4 +45,4 @@ module RShade
|
|
40
45
|
end
|
41
46
|
end
|
42
47
|
end
|
43
|
-
end
|
48
|
+
end
|
@@ -1,10 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RShade
|
2
4
|
module Filter
|
3
5
|
class VariableFilter < AbstractFilter
|
4
6
|
attr_reader :matchers
|
7
|
+
|
5
8
|
NAME = :variable_filter
|
6
9
|
|
7
10
|
def initialize
|
11
|
+
super
|
8
12
|
@matchers = []
|
9
13
|
end
|
10
14
|
|
@@ -30,4 +34,4 @@ module RShade
|
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
33
|
-
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RShade
|
4
|
+
module Formatter
|
5
|
+
module Stack
|
6
|
+
class Json
|
7
|
+
attr_reader :filepath, :pretty
|
8
|
+
|
9
|
+
def initialize(filepath:, pretty: false)
|
10
|
+
@filepath = filepath
|
11
|
+
@pretty = pretty
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Array<RShade::StackFrame>] stack_frames
|
15
|
+
def call(stack_frames)
|
16
|
+
payload = stack_frames.map.with_index do |frame, idx|
|
17
|
+
serialize(idx, frame)
|
18
|
+
end
|
19
|
+
|
20
|
+
File.open(filepath, 'a+') do |file|
|
21
|
+
record = {
|
22
|
+
time: Time.now.getutc,
|
23
|
+
thread_id: Thread.current,
|
24
|
+
thread_list: Thread.list,
|
25
|
+
frames: payload
|
26
|
+
}
|
27
|
+
file.puts(convert_to_json(record, pretty))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def convert_to_json(object, pretty)
|
34
|
+
return JSON.pretty_generate(object) if pretty
|
35
|
+
|
36
|
+
JSON.generate(object)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [RShade::StackFrame] frame
|
40
|
+
def serialize(idx, frame)
|
41
|
+
{
|
42
|
+
frame: idx,
|
43
|
+
source_location: "#{frame.source_location[:path]}:#{frame.source_location[:line]}",
|
44
|
+
local_variables: frame.local_vars,
|
45
|
+
receiver_variables: frame.receiver_variables
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RShade
|
4
|
+
module Formatter
|
5
|
+
module Stack
|
6
|
+
class String
|
7
|
+
attr_reader :colorize
|
8
|
+
|
9
|
+
ROOT_SEP = "---\n"
|
10
|
+
|
11
|
+
def initialize(colorize: true)
|
12
|
+
@colorize = colorize
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [RShade::EventProcessor] stack
|
16
|
+
def call(stack)
|
17
|
+
buffer = StringIO.new
|
18
|
+
stack.each_with_index do |frame, idx|
|
19
|
+
if idx.zero?
|
20
|
+
buffer << ROOT_SEP
|
21
|
+
next
|
22
|
+
end
|
23
|
+
next unless frame
|
24
|
+
|
25
|
+
buffer.write line(idx, frame, idx)
|
26
|
+
end
|
27
|
+
buffer.string
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# @param [RShade::StackFrame] frame
|
33
|
+
def line(line_idx, frame, depth)
|
34
|
+
source_location = apply_color("(#{frame.source_location[:path]}:#{frame.source_location[:line]})", :green)
|
35
|
+
var_str = frame.local_vars.map do |_, val|
|
36
|
+
var_name = val[:name]
|
37
|
+
var_value = val[:value]
|
38
|
+
var_type = val[:type]
|
39
|
+
"#{var_type} #{var_name} => (#{var_value})"
|
40
|
+
end.join(', ')
|
41
|
+
colorized_var_str = apply_color(var_str, :blue)
|
42
|
+
"#{' ' * depth}[frame: #{line_idx}]#{source_location} => local vars: (#{colorized_var_str})\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply_color(str, color)
|
46
|
+
return str unless colorize
|
47
|
+
|
48
|
+
ColorizedString[str].colorize(color)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RShade
|
4
|
+
module Formatter
|
5
|
+
module Trace
|
6
|
+
class File
|
7
|
+
attr_reader :formatter
|
8
|
+
|
9
|
+
FILE_NAME = 'stacktrace.json'
|
10
|
+
|
11
|
+
def initialize(args = {})
|
12
|
+
@formatter = args.fetch(:format, Json)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [RShade::EventProcessor] event_store
|
16
|
+
def call(event_store)
|
17
|
+
data = formatter.call(event_store)
|
18
|
+
if formatter == Json
|
19
|
+
write_to_file(JSON.pretty_generate(data))
|
20
|
+
else
|
21
|
+
write_to_file(data.to_s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def write_to_file(data)
|
26
|
+
::File.write(::File.join(RShade::Config.store_dir, FILE_NAME), data)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module RShade
|
6
|
+
module Formatter
|
7
|
+
module Trace
|
8
|
+
class Html
|
9
|
+
attr_reader :formatter
|
10
|
+
|
11
|
+
FILE_NAME = 'stacktrace.html'
|
12
|
+
TEMPLATE = 'html/template.html.erb'
|
13
|
+
|
14
|
+
def initialize(args = {})
|
15
|
+
@formatter = args.fetch(:formatter, Json)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [RShade::EventProcessor] event_store
|
19
|
+
def call(event_store)
|
20
|
+
data = formatter.call(event_store)
|
21
|
+
erb_template = ERB.new(template)
|
22
|
+
content = erb_template.result_with_hash({ json: data.to_json })
|
23
|
+
write_to_file(content)
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_to_file(data)
|
27
|
+
::File.write(::File.join(RShade::Config.store_dir, FILE_NAME), data)
|
28
|
+
end
|
29
|
+
|
30
|
+
def template
|
31
|
+
@template ||= ::File.read(::File.join(::RShade::Config.root_dir, TEMPLATE))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RShade
|
4
|
+
module Formatter
|
5
|
+
module Trace
|
6
|
+
class Json
|
7
|
+
attr_reader :event_processor
|
8
|
+
|
9
|
+
# @param [RShade::EventProcessor] event_store
|
10
|
+
def call(event_store)
|
11
|
+
@event_store = event_store
|
12
|
+
flat
|
13
|
+
end
|
14
|
+
|
15
|
+
def flat
|
16
|
+
arr = event_store.map do |node|
|
17
|
+
item(node.event)
|
18
|
+
end
|
19
|
+
arr.sort_by { |item| item[:level] }
|
20
|
+
end
|
21
|
+
|
22
|
+
def hierarchical
|
23
|
+
hash = {}
|
24
|
+
event_store.each do |node|
|
25
|
+
depth = node.level
|
26
|
+
ref = hash_iterate(hash, depth)
|
27
|
+
ref[:data] = item(node)
|
28
|
+
end
|
29
|
+
sort_hash(hash)
|
30
|
+
end
|
31
|
+
|
32
|
+
def sort_hash(hash)
|
33
|
+
{}.tap do |h2|
|
34
|
+
hash.sort.each do |k, v|
|
35
|
+
h2[k] = v.is_a?(Hash) ? sort_hash(v) : v
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def hash_iterate(hash, depth)
|
41
|
+
(0..depth).each do |_lvl|
|
42
|
+
hash[:inner] = {} unless hash[:inner]
|
43
|
+
hash = hash[:inner]
|
44
|
+
end
|
45
|
+
hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def item(value)
|
49
|
+
{
|
50
|
+
class: value.klass.to_s,
|
51
|
+
method_name: value.method_name,
|
52
|
+
full_path: "#{value.path}:#{value.lineno}",
|
53
|
+
level: value.depth,
|
54
|
+
vars: value.vars
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RShade
|
4
|
+
module Formatter
|
5
|
+
module Trace
|
6
|
+
class String
|
7
|
+
ROOT_SEP = "---\n"
|
8
|
+
|
9
|
+
def initialize(opts = {}); end
|
10
|
+
|
11
|
+
# @param [RShade::EventProcessor] event_store
|
12
|
+
def call(event_store)
|
13
|
+
buffer = StringIO.new
|
14
|
+
event_store.each_with_index do |node, idx|
|
15
|
+
depth = node.level
|
16
|
+
event = node.value
|
17
|
+
if depth == 1
|
18
|
+
buffer << ROOT_SEP
|
19
|
+
next
|
20
|
+
end
|
21
|
+
next unless event
|
22
|
+
|
23
|
+
buffer.write line(idx, event, node.vlevel)
|
24
|
+
end
|
25
|
+
buffer.string
|
26
|
+
end
|
27
|
+
|
28
|
+
def line(line_idx, value, depth)
|
29
|
+
vars = value.vars
|
30
|
+
returned_value = value.return_value || {}
|
31
|
+
returned_str = "#{returned_value[:type]} #{returned_value[:value]}"
|
32
|
+
returned = ColorizedString["=> |#{returned_str}|"].colorize(:magenta)
|
33
|
+
|
34
|
+
class_method = ColorizedString["#{value.klass}##{value.method_name}"].colorize(:green)
|
35
|
+
full_path = ColorizedString["#{value.path}:#{value.lineno}"].colorize(:blue)
|
36
|
+
line_idx = ColorizedString["[#{line_idx}] "].colorize(:red)
|
37
|
+
var_str = vars.map do |_, val|
|
38
|
+
var_name = val[:name]
|
39
|
+
var_value = val[:value]
|
40
|
+
var_type = val[:type]
|
41
|
+
"#{var_type} #{var_name} => (#{var_value})"
|
42
|
+
end.join(', ')
|
43
|
+
"#{' ' * depth}#{line_idx}#{class_method}(#{var_str}) #{returned} -> #{full_path}\n"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/rshade/rspec/rspec.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RShade
|
4
|
+
# rubocop:disable Style/MutableConstant
|
2
5
|
REPORTS = []
|
6
|
+
# rubocop:enable Style/MutableConstant
|
3
7
|
|
4
8
|
module RSpecHelper
|
5
|
-
def rshade_reveal(options = {})
|
9
|
+
def rshade_reveal(options = {}, &block)
|
6
10
|
raise 'No block given' unless block_given?
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
+
|
12
|
+
options.merge!(formatter: Formatter::String) { |_key, v1, _v2| v1 }
|
13
|
+
result = Trace.reveal(options, &block)
|
11
14
|
REPORTS.push result.show
|
12
15
|
end
|
13
16
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RShade
|
2
4
|
module Serializer
|
3
5
|
class Traversal
|
4
6
|
attr_reader :types
|
5
7
|
|
6
|
-
def initialize(custom_types={})
|
8
|
+
def initialize(custom_types = {})
|
7
9
|
@types = default_types.merge(custom_types)
|
8
10
|
end
|
9
11
|
|
@@ -17,31 +19,31 @@ module RShade
|
|
17
19
|
|
18
20
|
def default_types
|
19
21
|
{
|
20
|
-
default:
|
22
|
+
default: lambda(&:to_s),
|
21
23
|
Integer => ->(value) { value },
|
22
24
|
Float => ->(value) { value },
|
23
25
|
Numeric => ->(value) { value },
|
24
26
|
String => ->(value) { value },
|
25
|
-
Hash =>
|
27
|
+
Hash => lambda do |value|
|
26
28
|
hash = {}
|
27
|
-
value.each do |k,v|
|
29
|
+
value.each do |k, v|
|
28
30
|
hash[k] = traverse(v)
|
29
31
|
end
|
30
32
|
hash
|
31
33
|
end,
|
32
|
-
Array =>
|
34
|
+
Array => lambda do |value|
|
33
35
|
value.map { |item| traverse(item) }
|
34
36
|
end
|
35
37
|
}
|
36
38
|
end
|
37
39
|
|
38
40
|
def traverse(value)
|
39
|
-
klass =
|
41
|
+
klass = Class
|
40
42
|
klass = value.class unless value.is_a?(Class)
|
41
43
|
serializer = types[klass]
|
42
|
-
serializer
|
44
|
+
serializer ||= types[:default]
|
43
45
|
serializer.call(value)
|
44
46
|
end
|
45
47
|
end
|
46
48
|
end
|
47
|
-
end
|
49
|
+
end
|
data/lib/rshade/stack.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RShade
|
4
|
+
class Stack
|
5
|
+
attr_reader :config
|
6
|
+
|
7
|
+
def initialize(config: nil, registry: ::RShade::Config::Registry.instance)
|
8
|
+
@config = config || registry.default_stack_config
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.trace(config: nil)
|
12
|
+
new(config: config).trace
|
13
|
+
end
|
14
|
+
|
15
|
+
def trace
|
16
|
+
config.exclude_gems!
|
17
|
+
result = binding.callers.drop(2).map do |bind|
|
18
|
+
frame = StackFrame.from_binding(bind)
|
19
|
+
next nil unless config.filter.call(frame)
|
20
|
+
|
21
|
+
frame
|
22
|
+
end.compact.reverse
|
23
|
+
config.formatter.call(result)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RShade
|
4
|
+
# nodoc
|
5
|
+
class StackFrame
|
6
|
+
attr_reader :hash
|
7
|
+
|
8
|
+
def initialize(hash)
|
9
|
+
@hash = hash
|
10
|
+
end
|
11
|
+
|
12
|
+
%i[source_location source local_vars receiver_variables].each do |method_name|
|
13
|
+
define_method method_name do
|
14
|
+
fetch method_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def path
|
19
|
+
source_location[:path]
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Binding] binding_frame
|
23
|
+
def self.from_binding(binding_frame)
|
24
|
+
source_location = {
|
25
|
+
path: binding_frame.source_location[0],
|
26
|
+
line: binding_frame.source_location[1]
|
27
|
+
}
|
28
|
+
local_vars = binding_frame.local_variables.each_with_object({}) do |var_name, memo|
|
29
|
+
value = binding_frame.local_variable_get(var_name)
|
30
|
+
type = value.is_a?(Class) ? value : value.class
|
31
|
+
memo[var_name] = {
|
32
|
+
name: var_name,
|
33
|
+
value: value,
|
34
|
+
type: type.to_s
|
35
|
+
}
|
36
|
+
end
|
37
|
+
receiver_variables = binding_frame.receiver.instance_variables.each_with_object({}) do |var_name, memo|
|
38
|
+
value = binding_frame.receiver.instance_variable_get(var_name)
|
39
|
+
type = value.is_a?(Class) ? value : value.class
|
40
|
+
memo[var_name] = {
|
41
|
+
name: var_name,
|
42
|
+
value: value,
|
43
|
+
type: type.to_s
|
44
|
+
}
|
45
|
+
end
|
46
|
+
hash = { source_location: source_location, local_vars: local_vars, source: {},
|
47
|
+
receiver_variables: receiver_variables }
|
48
|
+
new(hash)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
"#{source_location} - #{local_vars} - #{source}"
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def fetch(key)
|
58
|
+
@hash[key]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|