rshade 0.1.9.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|