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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.rubocop.yml +31 -0
  4. data/Gemfile +9 -2
  5. data/Gemfile.lock +11 -2
  6. data/README.md +20 -3
  7. data/Rakefile +5 -3
  8. data/bin/console +4 -3
  9. data/lib/rshade/config/registry.rb +40 -0
  10. data/lib/rshade/config/stack_store.rb +63 -0
  11. data/lib/rshade/config/store.rb +12 -7
  12. data/lib/rshade/config.rb +8 -7
  13. data/lib/rshade/core_extensions/object/reveal.rb +37 -5
  14. data/lib/rshade/event.rb +15 -5
  15. data/lib/rshade/event_observer.rb +3 -1
  16. data/lib/rshade/event_processor.rb +12 -11
  17. data/lib/rshade/event_tree.rb +18 -18
  18. data/lib/rshade/filter/abstract_filter.rb +3 -1
  19. data/lib/rshade/filter/default.rb +4 -2
  20. data/lib/rshade/filter/exclude_path_filter.rb +5 -3
  21. data/lib/rshade/filter/filter_builder.rb +14 -3
  22. data/lib/rshade/filter/filter_composition.rb +29 -25
  23. data/lib/rshade/filter/include_path_filter.rb +6 -1
  24. data/lib/rshade/filter/variable_filter.rb +5 -1
  25. data/lib/rshade/formatter/stack/json.rb +51 -0
  26. data/lib/rshade/formatter/stack/stdout.rb +13 -0
  27. data/lib/rshade/formatter/stack/string.rb +53 -0
  28. data/lib/rshade/formatter/trace/file.rb +31 -0
  29. data/lib/rshade/formatter/trace/html.rb +36 -0
  30. data/lib/rshade/formatter/trace/json.rb +60 -0
  31. data/lib/rshade/formatter/trace/stdout.rb +14 -0
  32. data/lib/rshade/formatter/trace/string.rb +48 -0
  33. data/lib/rshade/rspec/rspec.rb +8 -5
  34. data/lib/rshade/serializer/traversal.rb +10 -8
  35. data/lib/rshade/stack.rb +26 -0
  36. data/lib/rshade/stack_frame.rb +61 -0
  37. data/lib/rshade/trace.rb +7 -4
  38. data/lib/rshade/trace_observable.rb +10 -5
  39. data/lib/rshade/version.rb +3 -1
  40. data/lib/rshade.rb +25 -8
  41. data/rshade.gemspec +24 -22
  42. metadata +29 -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
  48. 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
- !paths.any? do |path|
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 = traverse(arg1) if arg1.is_a?(Array)
22
- arg2 = traverse(arg2) if arg2.is_a?(Array)
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 :op, :left, :right, :parent
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(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
 
21
+ # rubocop:disable Metrics/CyclomaticComplexity
19
22
  def call(event)
20
- case op
23
+ case value
21
24
  when UNARY_OP
22
- return left&.call(event)
25
+ left&.call(event)
23
26
  when AND_OP
24
- return left&.call(event) && right&.call(event)
27
+ left&.call(event) && right&.call(event)
25
28
  when OR_OP
26
- l = left&.call(event)
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
- raise 'undefined op'
31
+ value.call(event)
32
32
  end
33
33
  end
34
+ # rubocop:enable Metrics/CyclomaticComplexity
34
35
 
35
36
  def each(&block)
36
- if left&.respond_to?(:each)
37
- left&.each(&block)
38
- else
39
- yield left
40
- end
37
+ yield value unless left && right
38
+ left&.each(&block)
39
+ right&.each(&block)
40
+ end
41
41
 
42
- if right&.respond_to?(:each)
43
- right&.each(&block)
44
- else
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
- def config_filter(type, &block)
50
- filter = find do |filter|
51
- filter.is_a? type
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
- filter.config(&block) if filter
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,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
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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,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
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,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
- options.merge!(formatter: Formatter::String) { |_key,v1, _v2| v1 }
8
- result = Trace.reveal(options) do
9
- yield
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: ->(value) { value.to_s },
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 => ->(value) do
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 => ->(value) do
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 = value
41
+ klass = Class
40
42
  klass = value.class unless value.is_a?(Class)
41
43
  serializer = types[klass]
42
- serializer = types[:default] unless serializer
44
+ serializer ||= types[:default]
43
45
  serializer.call(value)
44
46
  end
45
47
  end
46
48
  end
47
- end
49
+ 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: 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