rshade 0.1.9 → 0.2.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/Gemfile +6 -2
  4. data/Gemfile.lock +10 -1
  5. data/README.md +19 -3
  6. data/Rakefile +5 -3
  7. data/bin/console +4 -3
  8. data/lib/rshade/config/registry.rb +47 -0
  9. data/lib/rshade/config/stack_store.rb +58 -0
  10. data/lib/rshade/config/store.rb +14 -5
  11. data/lib/rshade/config.rb +9 -3
  12. data/lib/rshade/core_extensions/object/reveal.rb +34 -5
  13. data/lib/rshade/event.rb +15 -9
  14. data/lib/rshade/event_observer.rb +3 -1
  15. data/lib/rshade/event_processor.rb +14 -12
  16. data/lib/rshade/event_tree.rb +13 -15
  17. data/lib/rshade/filter/abstract_filter.rb +3 -1
  18. data/lib/rshade/filter/default.rb +3 -1
  19. data/lib/rshade/filter/exclude_path_filter.rb +10 -7
  20. data/lib/rshade/filter/filter_builder.rb +14 -3
  21. data/lib/rshade/filter/filter_composition.rb +26 -24
  22. data/lib/rshade/filter/include_path_filter.rb +5 -1
  23. data/lib/rshade/filter/variable_filter.rb +4 -1
  24. data/lib/rshade/formatter/stack/json.rb +51 -0
  25. data/lib/rshade/formatter/stack/stdout.rb +13 -0
  26. data/lib/rshade/formatter/stack/string.rb +41 -0
  27. data/lib/rshade/formatter/trace/file.rb +33 -0
  28. data/lib/rshade/formatter/trace/html.rb +38 -0
  29. data/lib/rshade/formatter/trace/json.rb +61 -0
  30. data/lib/rshade/formatter/trace/stdout.rb +14 -0
  31. data/lib/rshade/formatter/trace/string.rb +48 -0
  32. data/lib/rshade/rspec/rspec.rb +7 -6
  33. data/lib/rshade/serializer/traversal.rb +54 -0
  34. data/lib/rshade/stack.rb +26 -0
  35. data/lib/rshade/stack_frame.rb +60 -0
  36. data/lib/rshade/trace.rb +8 -5
  37. data/lib/rshade/trace_observable.rb +5 -0
  38. data/lib/rshade/version.rb +3 -1
  39. data/lib/rshade.rb +26 -10
  40. data/rshade.gemspec +24 -21
  41. metadata +44 -9
  42. data/lib/rshade/binding_serializer.rb +0 -35
  43. data/lib/rshade/formatter/file.rb +0 -28
  44. data/lib/rshade/formatter/html.rb +0 -33
  45. data/lib/rshade/formatter/json.rb +0 -59
  46. data/lib/rshade/formatter/stdout.rb +0 -10
  47. data/lib/rshade/formatter/string.rb +0 -36
@@ -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,53 @@ module RShade
5
7
  AND_OP = :and
6
8
  OR_OP = :or
7
9
  UNARY_OP = :unary
8
- attr_reader :op, :left, :right, :parent
10
+ attr_reader :value, :left, :right, :parent
9
11
  attr_accessor :parent
10
12
 
11
13
  # @param [#call, Enumerable] left
12
14
  # @param [#call, Enumerable] right
13
- def initialize(op=UNARY_OP, left=nil, right=nil)
14
- @op = op
15
+ def initialize(value, left = nil, right = nil)
16
+ @value = value
15
17
  @left = left
16
18
  @right = right
17
19
  end
18
20
 
19
21
  def call(event)
20
- case op
22
+ case value
21
23
  when UNARY_OP
22
- return left&.call(event)
24
+ left&.call(event)
23
25
  when AND_OP
24
- return left&.call(event) && right&.call(event)
26
+ left&.call(event) && right&.call(event)
25
27
  when OR_OP
26
- l = left&.call(event)
27
- r = right&.call(event)
28
- # puts "#{left} => #{l} OR #{right} => #{r}"
29
- return l || r
28
+ left&.call(event) || right&.call(event)
30
29
  else
31
- raise 'undefined op'
30
+ value.call(event)
32
31
  end
33
32
  end
34
33
 
35
34
  def each(&block)
36
- if left&.respond_to?(:each)
37
- left&.each(&block)
38
- else
39
- yield left
40
- end
41
-
42
- if right&.respond_to?(:each)
43
- right&.each(&block)
44
- else
45
- yield right
46
- end
35
+ yield value unless left && right
36
+ left&.each(&block)
37
+ right&.each(&block)
47
38
  end
48
39
 
49
40
  def config_filter(type, &block)
50
41
  filter = find do |filter|
51
42
  filter.is_a? type
52
43
  end
53
- filter.config(&block) if filter
44
+ filter&.config(&block)
45
+ end
46
+
47
+ # for debug purposes, show each filter and result of evaluation
48
+ def filter_results(event)
49
+ each_with_object([]) do |filter, arr|
50
+ arr << [filter, filter.call(event)]
51
+ end
52
+ end
53
+
54
+ def self.build(arr)
55
+ ::RShade::Filter::FilterBuilder.build(arr)
54
56
  end
55
57
  end
56
58
  end
57
- end
59
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  module Filter
3
5
  class IncludePathFilter < AbstractFilter
@@ -22,6 +24,7 @@ module RShade
22
24
  paths.any? do |path|
23
25
  next str?(path, event_path) if path.is_a? String
24
26
  next regexp?(path, event_path) if path.is_a? Regexp
27
+
25
28
  false
26
29
  end
27
30
  end
@@ -31,6 +34,7 @@ module RShade
31
34
  end
32
35
 
33
36
  private
37
+
34
38
  def str?(str, event_path)
35
39
  event_path.include?(str)
36
40
  end
@@ -40,4 +44,4 @@ module RShade
40
44
  end
41
45
  end
42
46
  end
43
- end
47
+ end
@@ -1,7 +1,10 @@
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
@@ -30,4 +33,4 @@ module RShade
30
33
  end
31
34
  end
32
35
  end
33
- end
36
+ 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Stack
6
+ class Stdout < String
7
+ def call(stack)
8
+ puts super(stack)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Stack
6
+ class String
7
+ ROOT_SEP = "---\n"
8
+
9
+ def initialize(opts = {}); end
10
+
11
+ # @param [RShade::EventProcessor] stack
12
+ def call(stack)
13
+ buffer = StringIO.new
14
+ stack.each_with_index do |frame, idx|
15
+ if idx.zero?
16
+ buffer << ROOT_SEP
17
+ next
18
+ end
19
+ next unless frame
20
+
21
+ buffer.write line(idx, frame, idx)
22
+ end
23
+ buffer.string
24
+ end
25
+
26
+ # @param [RShade::StackFrame] frame
27
+ def line(line_idx, frame, depth)
28
+ source_location = ColorizedString["(#{frame.source_location[:path]}:#{frame.source_location[:line]})"].colorize(:green)
29
+ var_str = frame.local_vars.map do |_, val|
30
+ var_name = val[:name]
31
+ var_value = val[:value]
32
+ var_type = val[:type]
33
+ "#{var_type} #{var_name} => (#{var_value})"
34
+ end.join(', ')
35
+ colorized_var_str = ColorizedString[var_str].colorize(:blue)
36
+ "#{' ' * depth}[frame: #{line_idx}]#{source_location} => local vars: (#{colorized_var_str})\n"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
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.open(::File.join(RShade::Config.store_dir, FILE_NAME), 'w+') do |f|
27
+ f.write data
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
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.open(::File.join(RShade::Config.store_dir, FILE_NAME), 'w+') do |f|
28
+ f.write data
29
+ end
30
+ end
31
+
32
+ def template
33
+ @template ||= ::File.read(::File.join(::RShade::Config.root_dir, TEMPLATE))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,61 @@
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 = []
17
+ event_store.each do |node|
18
+ arr << item(node.event)
19
+ end
20
+ arr.sort_by { |item| item[:level] }
21
+ end
22
+
23
+ def hierarchical
24
+ hash = {}
25
+ event_store.each do |node|
26
+ depth = node.level
27
+ ref = hash_iterate(hash, depth)
28
+ ref[:data] = item(node)
29
+ end
30
+ sort_hash(hash)
31
+ end
32
+
33
+ def sort_hash(h)
34
+ {}.tap do |h2|
35
+ h.sort.each do |k, v|
36
+ h2[k] = v.is_a?(Hash) ? sort_hash(v) : v
37
+ end
38
+ end
39
+ end
40
+
41
+ def hash_iterate(hash, depth)
42
+ (0..depth).each do |_lvl|
43
+ hash[:inner] = {} unless hash[:inner]
44
+ hash = hash[:inner]
45
+ end
46
+ hash
47
+ end
48
+
49
+ def item(value)
50
+ {
51
+ class: value.klass.to_s,
52
+ method_name: value.method_name,
53
+ full_path: "#{value.path}:#{value.lineno}",
54
+ level: value.depth,
55
+ vars: value.vars
56
+ }
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Formatter
5
+ module Trace
6
+ class Stdout < String
7
+ # @param [RShade::EventProcessor] event_store
8
+ def call(event_store)
9
+ puts super(event_store)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ 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
@@ -1,13 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
- REPORTS = []
4
+ REPORTS = [].freeze
3
5
 
4
6
  module RSpecHelper
5
- def rshade_reveal(options = {})
7
+ def rshade_reveal(options = {}, &block)
6
8
  raise 'No block given' unless block_given?
7
- options.merge!(formatter: Formatter::String) { |_key,v1, _v2| v1 }
8
- result = Trace.reveal(options) do
9
- yield
10
- end
9
+
10
+ options.merge!(formatter: Formatter::String) { |_key, v1, _v2| v1 }
11
+ result = Trace.reveal(options, &block)
11
12
  REPORTS.push result.show
12
13
  end
13
14
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RShade
4
+ module Serializer
5
+ class Traversal
6
+ attr_reader :types
7
+
8
+ def initialize(custom_types = {})
9
+ @types = default_types.merge(custom_types)
10
+ end
11
+
12
+ def call(hash)
13
+ new_val = {}
14
+ hash.each do |name, value|
15
+ new_val[name] = traverse(value)
16
+ end
17
+ new_val
18
+ end
19
+
20
+ def default_types
21
+ {
22
+ default: lambda(&:to_s),
23
+ Integer => ->(value) { value },
24
+ Float => ->(value) { value },
25
+ Numeric => ->(value) { value },
26
+ String => ->(value) { value },
27
+ Hash => lambda do |value|
28
+ hash = {}
29
+ begin
30
+ value.each do |k, v|
31
+ hash[k] = traverse(v)
32
+ end
33
+ rescue Exception => e
34
+ binding.pry
35
+ end
36
+
37
+ hash
38
+ end,
39
+ Array => lambda do |value|
40
+ value.map { |item| traverse(item) }
41
+ end
42
+ }
43
+ end
44
+
45
+ def traverse(value)
46
+ klass = Class
47
+ klass = value.class unless value.is_a?(Class)
48
+ serializer = types[klass]
49
+ serializer ||= types[:default]
50
+ serializer.call(value)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -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:).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,60 @@
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:,
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:,
43
+ type: type.to_s
44
+ }
45
+ end
46
+ hash = { source_location:, local_vars:, source: {}, receiver_variables: }
47
+ new(hash)
48
+ end
49
+
50
+ def to_s
51
+ "#{source_location} - #{local_vars} - #{source}"
52
+ end
53
+
54
+ private
55
+
56
+ def fetch(key)
57
+ @hash[key]
58
+ end
59
+ end
60
+ end
data/lib/rshade/trace.rb CHANGED
@@ -1,22 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  class Trace
3
5
  attr_reader :config, :event_store
4
-
6
+
5
7
  # @param [RShade::Config,RShade::Config::Store] config
6
8
  def initialize(config)
7
9
  @config = fetch_config(config)
8
10
  @event_store = EventTree.new
9
11
  end
10
12
 
11
- def self.reveal(config=nil, &block)
13
+ def self.reveal(config = nil, &block)
12
14
  new(config).reveal(&block)
13
15
  end
14
16
 
15
17
  def reveal(&block)
16
- processor = EventProcessor.new(@event_store)
18
+ processor = EventProcessor.new(event_store, config)
17
19
  observer = EventObserver.new(config, processor)
18
20
  observable = RShade::TraceObservable.new([observer], config)
19
- observable.reveal &block
21
+ observable.reveal(&block)
20
22
  self
21
23
  end
22
24
 
@@ -25,8 +27,9 @@ module RShade
25
27
  end
26
28
 
27
29
  private
30
+
28
31
  def fetch_config(config)
29
- config = config || ::RShade::Config.default
32
+ config ||= ::RShade::Config.default
30
33
  config = config.value if config.is_a?(::RShade::Config)
31
34
  config
32
35
  end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
4
  class TraceObservable
3
5
  include Observable
4
6
  attr_reader :trace_p
7
+
5
8
  CALL_EVENTS = Set[:call, :c_call, :b_call]
6
9
  RETURN_EVENTS = Set[:return, :c_return, :b_return]
7
10
 
@@ -27,6 +30,7 @@ module RShade
27
30
  end
28
31
 
29
32
  private
33
+
30
34
  # more info https://rubyapi.org/3.1/o/tracepoint
31
35
  # @param [TracePoint] tp
32
36
  def process(tp)
@@ -34,6 +38,7 @@ module RShade
34
38
  event = Event.from_trace_point(tp)
35
39
  return notify_observers(event, :enter) if CALL_EVENTS.include?(tp.event)
36
40
  return notify_observers(event, :leave) if RETURN_EVENTS.include?(tp.event)
41
+
37
42
  notify_observers(event, :other)
38
43
  end
39
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RShade
2
- VERSION = "0.1.9"
4
+ VERSION = '0.2.0'
3
5
  end