rshade 0.1.6 → 0.1.9

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.
@@ -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