rshade 0.1.6 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ module RShade
2
+ module Filter
3
+ class Default
4
+ RUBY_VERSION_PATTERN = /ruby-[0-9.]*/
5
+
6
+ def self.create
7
+ new.create
8
+ end
9
+
10
+ def create
11
+ [create_exclude]
12
+ end
13
+
14
+ def create_exclude
15
+ filter = ExcludePathFilter.new
16
+ filter.config do |paths|
17
+ excluded_paths.each do |path|
18
+ paths << path
19
+ end
20
+ end
21
+ end
22
+
23
+ def excluded_paths
24
+ [ENV['GEM_PATH'].split(':'), RUBY_VERSION_PATTERN, /internal/].flatten.compact
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module RShade
2
+ module Filter
3
+ class ExcludePathFilter < IncludePathFilter
4
+ NAME = :exclude_paths
5
+
6
+ def name
7
+ NAME
8
+ end
9
+
10
+ def priority
11
+ 0
12
+ end
13
+
14
+ private
15
+ def str?(str, event_path)
16
+ !event_path.include?(str)
17
+ end
18
+
19
+ def regexp?(regex, event_path)
20
+ !regex.match?(event_path)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ module RShade
2
+ module Filter
3
+ class FilterBuilder
4
+ def self.build(arr)
5
+ new.traverse(arr)
6
+ end
7
+
8
+ def map
9
+ {
10
+ or: [RShade::Filter::FilterComposition::OR_OP, 2],
11
+ and: [RShade::Filter::FilterComposition::AND_OP, 2],
12
+ unary: [RShade::Filter::FilterComposition::UNARY_OP, 1]
13
+ }
14
+ end
15
+
16
+ def traverse(arr)
17
+ op, arity = map[arr[0]]
18
+ arg1 = arr[1]
19
+ arg2 = nil
20
+ 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
+
24
+ RShade::Filter::FilterComposition.new(op, arg1, arg2)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,57 @@
1
+ module RShade
2
+ module Filter
3
+ class FilterComposition
4
+ include Enumerable
5
+ AND_OP = :and
6
+ OR_OP = :or
7
+ UNARY_OP = :unary
8
+ attr_reader :op, :left, :right, :parent
9
+ attr_accessor :parent
10
+
11
+ # @param [#call, Enumerable] left
12
+ # @param [#call, Enumerable] right
13
+ def initialize(op=UNARY_OP, left=nil, right=nil)
14
+ @op = op
15
+ @left = left
16
+ @right = right
17
+ end
18
+
19
+ def call(event)
20
+ case op
21
+ when UNARY_OP
22
+ return left&.call(event)
23
+ when AND_OP
24
+ return left&.call(event) && right&.call(event)
25
+ when OR_OP
26
+ l = left&.call(event)
27
+ r = right&.call(event)
28
+ # puts "#{left} => #{l} OR #{right} => #{r}"
29
+ return l || r
30
+ else
31
+ raise 'undefined op'
32
+ end
33
+ end
34
+
35
+ 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
47
+ end
48
+
49
+ def config_filter(type, &block)
50
+ filter = find do |filter|
51
+ filter.is_a? type
52
+ end
53
+ filter.config(&block) if filter
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,43 @@
1
+ module RShade
2
+ module Filter
3
+ class IncludePathFilter < AbstractFilter
4
+ attr_reader :paths
5
+
6
+ NAME = :include_paths
7
+
8
+ def initialize
9
+ @paths = []
10
+ end
11
+
12
+ def name
13
+ NAME
14
+ end
15
+
16
+ def priority
17
+ 1
18
+ end
19
+
20
+ def call(event)
21
+ event_path = event.path
22
+ paths.any? do |path|
23
+ next str?(path, event_path) if path.is_a? String
24
+ next regexp?(path, event_path) if path.is_a? Regexp
25
+ false
26
+ end
27
+ end
28
+
29
+ def config_call(&block)
30
+ block.call(@paths)
31
+ end
32
+
33
+ private
34
+ def str?(str, event_path)
35
+ event_path.include?(str)
36
+ end
37
+
38
+ def regexp?(regex, event_path)
39
+ regex.match?(event_path)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ module RShade
2
+ module Filter
3
+ class VariableFilter < AbstractFilter
4
+ attr_reader :matchers
5
+ NAME = :variable_filter
6
+
7
+ def initialize
8
+ @matchers = []
9
+ end
10
+
11
+ def name
12
+ :variable_filter
13
+ end
14
+
15
+ def priority
16
+ 2
17
+ end
18
+
19
+ def call(event)
20
+ matchers.each do |match|
21
+ event.vars.each do |name, value|
22
+ return true if match.call(name, value)
23
+ end
24
+ end
25
+ false
26
+ end
27
+
28
+ def config_call(&block)
29
+ matchers << block
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ module RShade
2
+ module Formatter
3
+ class File
4
+ attr_reader :formatter
5
+ FILE_NAME = 'stacktrace.json'.freeze
6
+
7
+ def initialize(args={})
8
+ @formatter = args.fetch(:format, Json)
9
+ end
10
+
11
+ # @param [RShade::EventProcessor] event_store
12
+ def call(event_store)
13
+ data = formatter.call(event_store)
14
+ if formatter == Json
15
+ write_to_file(JSON.pretty_generate(data))
16
+ else
17
+ write_to_file(data.to_s)
18
+ end
19
+ end
20
+
21
+ def write_to_file(data)
22
+ ::File.open(::File.join(RShade::Config.store_dir, FILE_NAME), "w+") do |f|
23
+ f.write data
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ require 'json'
2
+
3
+ module RShade
4
+ module Formatter
5
+ class Html
6
+ attr_reader :formatter
7
+ FILE_NAME = 'stacktrace.html'.freeze
8
+ TEMPLATE = 'html/template.html.erb'
9
+
10
+ def initialize(args={})
11
+ @formatter = args.fetch(:formatter, Json)
12
+ end
13
+
14
+ # @param [RShade::EventProcessor] event_store
15
+ def call(event_store)
16
+ data = formatter.call(event_store)
17
+ erb_template = ERB.new(template)
18
+ content = erb_template.result_with_hash({json: data.to_json})
19
+ write_to_file(content)
20
+ end
21
+
22
+ def write_to_file(data)
23
+ ::File.open(::File.join(RShade::Config.store_dir, FILE_NAME), "w+") do |f|
24
+ f.write data
25
+ end
26
+ end
27
+
28
+ def template
29
+ @template ||=::File.read(::File.join(::RShade::Config.root_dir, TEMPLATE))
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,59 @@
1
+ module RShade
2
+ module Formatter
3
+ class Json
4
+ attr_reader :event_processor
5
+
6
+ # @param [RShade::EventProcessor] event_store
7
+ def call(event_store)
8
+ @event_store = event_store
9
+ flat
10
+ end
11
+
12
+ def flat
13
+ arr = []
14
+ event_store.each do |node|
15
+ arr << item(node.event)
16
+ end
17
+ arr.sort_by { |item| item[:level]}
18
+ end
19
+
20
+ def hierarchical
21
+ hash = {}
22
+ event_store.each do |node|
23
+ depth = node.level
24
+ ref = hash_iterate(hash, depth)
25
+ ref[:data] = item(node)
26
+ end
27
+ sort_hash(hash)
28
+ end
29
+
30
+ def sort_hash(h)
31
+ {}.tap do |h2|
32
+ h.sort.each do |k,v|
33
+ h2[k] = v.is_a?(Hash) ? sort_hash(v) : v
34
+ end
35
+ end
36
+ end
37
+
38
+ def hash_iterate(hash, depth)
39
+ (0..depth).each do |lvl|
40
+ unless hash[:inner]
41
+ hash[:inner] = {}
42
+ end
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
@@ -0,0 +1,10 @@
1
+ module RShade
2
+ module Formatter
3
+ class Stdout < String
4
+ # @param [RShade::EventProcessor] event_store
5
+ def call(event_store)
6
+ puts super(event_store)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,36 @@
1
+ module RShade
2
+ module Formatter
3
+ class String
4
+ ROOT_SEP = "---\n"
5
+
6
+ def initialize(opts= {})
7
+ end
8
+
9
+ # @param [RShade::EventProcessor] event_store
10
+ def call(event_store)
11
+ buffer = StringIO.new
12
+ event_store.each_with_index do |node, idx|
13
+ depth = node.level
14
+ event = node.value
15
+ if depth == 1
16
+ buffer << ROOT_SEP
17
+ next
18
+ end
19
+ next unless event
20
+ buffer.write line(idx, event, node.vlevel)
21
+ end
22
+ buffer.string
23
+ end
24
+
25
+ def line(line_idx, value, depth)
26
+ vars = value.vars
27
+ returned = ColorizedString["=> |#{value.return_value}|"].colorize(:magenta)
28
+
29
+ class_method = ColorizedString["#{value.klass}##{value.method_name}"].colorize(:green)
30
+ full_path = ColorizedString["#{value.path}:#{value.lineno}"].colorize(:blue)
31
+ line_idx = ColorizedString["[#{line_idx}] "].colorize(:red)
32
+ "#{' ' * depth}#{line_idx}#{class_method}(#{vars}) #{returned} -> #{full_path}\n"
33
+ end
34
+ end
35
+ end
36
+ end
File without changes
@@ -2,15 +2,13 @@ module RShade
2
2
  REPORTS = []
3
3
 
4
4
  module RSpecHelper
5
- def rshade_reveal(type = ::RShade::APP_TRACE, options = {})
5
+ def rshade_reveal(options = {})
6
6
  raise 'No block given' unless block_given?
7
-
8
- trace = Trace.new
9
- trace.reveal(options) do
7
+ options.merge!(formatter: Formatter::String) { |_key,v1, _v2| v1 }
8
+ result = Trace.reveal(options) do
10
9
  yield
11
10
  end
12
-
13
- REPORTS.push trace.show(type)
11
+ REPORTS.push result.show
14
12
  end
15
13
  end
16
14
  end
data/lib/rshade/trace.rb CHANGED
@@ -1,69 +1,34 @@
1
1
  module RShade
2
2
  class Trace
3
- attr_accessor :source_tree, :options
4
- EVENTS = %i[call return].freeze
5
-
6
- def initialize
7
- @source_tree = SourceNode.new(nil)
8
- @tp = TracePoint.new(*EVENTS, &method(:process_trace))
9
- @stack = [@source_tree]
10
- end
11
-
12
- def reveal(options = {})
13
- return unless block_given?
14
-
15
- @tp.enable
16
- yield
17
- ensure
18
- @tp.disable
3
+ attr_reader :config, :event_store
4
+
5
+ # @param [RShade::Config,RShade::Config::Store] config
6
+ def initialize(config)
7
+ @config = fetch_config(config)
8
+ @event_store = EventTree.new
19
9
  end
20
10
 
21
- def show(type = ::RShade::APP_TRACE)
22
- return show_app_trace if type == ::RShade::APP_TRACE
23
-
24
- show_full_trace
11
+ def self.reveal(config=nil, &block)
12
+ new(config).reveal(&block)
25
13
  end
26
14
 
27
- def show_full_trace(tree = nil)
28
- buffer = StringIO.new
29
- tree ||= source_tree
30
- tree.pre_order_traverse do |node, depth|
31
- if node.root?
32
- buffer << "---\n"
33
- next
34
- end
35
-
36
- buffer << "#{' ' * depth} #{node.value.pretty}\n" if node.value
37
- end
38
- puts buffer.string
15
+ def reveal(&block)
16
+ processor = EventProcessor.new(@event_store)
17
+ observer = EventObserver.new(config, processor)
18
+ observable = RShade::TraceObservable.new([observer], config)
19
+ observable.reveal &block
20
+ self
39
21
  end
40
22
 
41
- def show_app_trace
42
- clone = source_tree.clone_by do |node|
43
- next true if node.root?
44
-
45
- node.value.app_code?
46
- end
47
- show_full_trace(clone)
23
+ def show
24
+ config.formatter.call(event_store)
48
25
  end
49
26
 
50
- def process_trace(tp)
51
- if tp.event == :call
52
- parent = @stack.last
53
- vars = {}
54
- tp.binding.local_variables.each do |var|
55
- vars[var] = tp.binding.local_variable_get var
56
- end
57
- hash = { level: @stack.size, path: tp.path, lineno: tp.lineno, klass: tp.defined_class, method_name: tp.method_id, vars: vars }
58
- node = SourceNode.new(Source.new(hash))
59
- node.parent = parent
60
- parent << node
61
- @stack.push node
62
- end
63
-
64
- if tp.event == :return && @stack.size > 1
65
- @stack.pop
66
- end
27
+ private
28
+ def fetch_config(config)
29
+ config = config || ::RShade::Config.default
30
+ config = config.value if config.is_a?(::RShade::Config)
31
+ config
67
32
  end
68
33
  end
69
34
  end
@@ -0,0 +1,40 @@
1
+ module RShade
2
+ class TraceObservable
3
+ include Observable
4
+ attr_reader :trace_p
5
+ CALL_EVENTS = Set[:call, :c_call, :b_call]
6
+ RETURN_EVENTS = Set[:return, :c_return, :b_return]
7
+
8
+ # @param [Enumerable<#call>, #call] observers
9
+ # @param [::RShade::Config::Store] config
10
+ def initialize(observers, config)
11
+ @trace_p = TracePoint.new(*config.tp_events, &method(:process))
12
+ observers = [observers] unless observers.is_a?(Enumerable)
13
+
14
+ observers.each do |observer|
15
+ add_observer(observer, :call)
16
+ end
17
+ end
18
+
19
+ def reveal
20
+ return unless block_given?
21
+
22
+ trace_p.enable
23
+ yield
24
+ self
25
+ ensure
26
+ trace_p.disable
27
+ end
28
+
29
+ private
30
+ # more info https://rubyapi.org/3.1/o/tracepoint
31
+ # @param [TracePoint] tp
32
+ def process(tp)
33
+ changed
34
+ event = Event.from_trace_point(tp)
35
+ return notify_observers(event, :enter) if CALL_EVENTS.include?(tp.event)
36
+ return notify_observers(event, :leave) if RETURN_EVENTS.include?(tp.event)
37
+ notify_observers(event, :other)
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
1
  module RShade
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.9"
3
3
  end
data/lib/rshade.rb CHANGED
@@ -1,26 +1,32 @@
1
- require 'colorize'
2
- require 'rshade/configuration'
3
- require 'rshade/source'
4
- require 'rshade/tree'
5
- require 'rshade/source_node'
1
+ require 'colorized_string'
2
+ require 'erb'
3
+ require 'weakref'
4
+ require 'set'
5
+ require 'observer'
6
+ require 'rshade/config'
7
+ require 'rshade/config/store'
8
+ require 'rshade/event_tree'
9
+ require 'rshade/event_processor'
10
+ require 'rshade/binding_serializer'
11
+ require 'rshade/event_observer'
12
+ require 'rshade/trace_observable'
13
+ require 'rshade/filter/abstract_filter'
14
+ require 'rshade/filter/filter_builder'
15
+ require 'rshade/filter/filter_composition'
16
+ require 'rshade/filter/include_path_filter'
17
+ require 'rshade/filter/exclude_path_filter'
18
+ require 'rshade/filter/variable_filter'
19
+ require 'rshade/filter/default'
20
+ require 'rshade/formatter/string'
21
+ require 'rshade/formatter/json'
22
+ require 'rshade/formatter/file'
23
+ require 'rshade/formatter/html'
24
+ require 'rshade/formatter/stdout'
25
+ require 'rshade/event'
6
26
  require 'rshade/trace'
7
- require 'rshade/rspec'
27
+ require 'rshade/rspec/rspec'
8
28
  require 'rshade/version'
9
29
 
10
30
 
11
31
  module RShade
12
- APP_TRACE = :app_trace
13
- FULL_TRACE = :full_trace
14
-
15
- class << self
16
- attr_writer :config
17
-
18
- def configuration
19
- @config ||= Configuration.new
20
- end
21
-
22
- def configure
23
- yield configuration
24
- end
25
- end
26
32
  end
data/rshade.gemspec CHANGED
@@ -38,7 +38,7 @@ Gem::Specification.new do |spec|
38
38
 
39
39
  spec.add_runtime_dependency 'colorize'
40
40
 
41
- spec.add_development_dependency "bundler", "~> 1.16"
42
- spec.add_development_dependency "rake", "~> 10.0"
41
+ spec.add_development_dependency "bundler", "~> 2.2.33"
42
+ spec.add_development_dependency "rake", ">= 12.3.3"
43
43
  spec.add_development_dependency "rspec", "~> 3.0"
44
44
  end
data/todo.md ADDED
@@ -0,0 +1,8 @@
1
+ 1. Should I use rails way organizing module/file naming or not?
2
+ 2. Reduce global state usage
3
+ 3. Problem with stack deep
4
+ 4. Problem when have binary values error in serialization (Encoding problems)
5
+ 5. Make filter while tracing that will be more efficient
6
+ 6. sort depth and than replace values
7
+ 7. tree not working bcz of vars serialization
8
+ 8. Some classes in Rails can't serialize safely produce StackTraceTooDeep