rshade 0.1.6 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6df1b60bffc16ead7fcebdeb882136144baa8e1fa33b2f4bad417dd3dfe68d92
4
- data.tar.gz: 072dcf363cfb6e9ac2112970fe133c8551fb9c3be7e02ad826ae8b19ca39a5f5
3
+ metadata.gz: e5ef0c31c59c3ed46f44326e1e28f6a5b2b2ccbdbce32152e6b54233f875d6d0
4
+ data.tar.gz: f394a28db1cda3274954309996eaea895bf715737f34aecbb09c0d0873fd09fb
5
5
  SHA512:
6
- metadata.gz: 9a4fec3f4b69197d872ff150aaf31e8202ada46b5a44d3b0d63ee8f831807f2db20db3e1a403e490ca3605e7d026099c51e4fdd55a8603e343a08b84fcb873ca
7
- data.tar.gz: a27714bfafda9b4d26adb96c135ea596d37a212586ede2a0df7cab2c751998cae205c16dfdb7d32fb903f2f343dfbf04dce73105f50e30ed98bea927083f8f5b
6
+ metadata.gz: 956a68bec41feea89513eab5e0002fadb70d9d0f9e57f1c47f316d2c30c6e801cb510a80312e54b9d075c4fa7f768ad6b2acc881dc3db03680a9351abe56dda4
7
+ data.tar.gz: 4bafb27714ffaf13734f99522897b3057da42f5bf4eb05929b824af9c67f19076f304373ab835f1cf0b0bb3ef5a253a1fd23ec343236596255b64f34290702b3
data/.gitignore CHANGED
@@ -6,6 +6,10 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ .idea
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
13
+ .ruby-version
14
+ .DS_Store
15
+ test.rb
data/Gemfile.lock CHANGED
@@ -1,43 +1,44 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rshade (0.1.6)
4
+ rshade (0.1.9)
5
5
  colorize
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- coderay (1.1.2)
10
+ coderay (1.1.3)
11
11
  colorize (0.8.1)
12
- diff-lcs (1.3)
13
- method_source (0.9.2)
14
- pry (0.12.2)
15
- coderay (~> 1.1.0)
16
- method_source (~> 0.9.0)
17
- rake (10.5.0)
18
- rspec (3.8.0)
19
- rspec-core (~> 3.8.0)
20
- rspec-expectations (~> 3.8.0)
21
- rspec-mocks (~> 3.8.0)
22
- rspec-core (3.8.0)
23
- rspec-support (~> 3.8.0)
24
- rspec-expectations (3.8.3)
12
+ diff-lcs (1.5.0)
13
+ method_source (1.0.0)
14
+ pry (0.14.1)
15
+ coderay (~> 1.1)
16
+ method_source (~> 1.0)
17
+ rake (13.0.6)
18
+ rspec (3.11.0)
19
+ rspec-core (~> 3.11.0)
20
+ rspec-expectations (~> 3.11.0)
21
+ rspec-mocks (~> 3.11.0)
22
+ rspec-core (3.11.0)
23
+ rspec-support (~> 3.11.0)
24
+ rspec-expectations (3.11.0)
25
25
  diff-lcs (>= 1.2.0, < 2.0)
26
- rspec-support (~> 3.8.0)
27
- rspec-mocks (3.8.0)
26
+ rspec-support (~> 3.11.0)
27
+ rspec-mocks (3.11.0)
28
28
  diff-lcs (>= 1.2.0, < 2.0)
29
- rspec-support (~> 3.8.0)
30
- rspec-support (3.8.0)
29
+ rspec-support (~> 3.11.0)
30
+ rspec-support (3.11.0)
31
31
 
32
32
  PLATFORMS
33
- ruby
33
+ x86_64-darwin-19
34
+ x86_64-darwin-20
34
35
 
35
36
  DEPENDENCIES
36
- bundler (~> 1.16)
37
+ bundler (~> 2.2.33)
37
38
  pry
38
- rake (~> 10.0)
39
+ rake (>= 12.3.3)
39
40
  rshade!
40
41
  rspec (~> 3.0)
41
42
 
42
43
  BUNDLED WITH
43
- 1.16.6
44
+ 2.2.33
data/README.md CHANGED
@@ -2,21 +2,82 @@
2
2
 
3
3
  ![warcraft shade](https://github.com/gingray/rshade/raw/master/shade.jpg)
4
4
 
5
- Ruby Shade or RShade gem to help you to reveal what lines of code are used in program execution.
6
-
5
+ RShade is a debugging/code exploration tool based on `TracePoint` functionality.
6
+ Recent years I've working with relatively huge legacy code and I need a tool which can help me to figure out what is going on due execution.
7
+ Luckely Ruby have build in functionality to achieve it, but it's pretty low level. It was my motivation to create `RShade` it helps me to save tons of time
8
+ when I face with something not trivial or a good start point to create dependency map when I do refactoring or bugfix. Tool still in beta and it's possible that there
9
+ is some bugs, but it's do the job.
10
+
11
+ ## How it works?
12
+ ```shell
13
+ gem install rshade
14
+ ```
15
+ Simple wrap code that you want to check in to block and it will pretty print all of the calls which was made in this block with pointing line of executions, variables what was pass
16
+ inside methods and variables what was return
7
17
  ```ruby
8
- trace = RShade::Trace.new
9
- trace.reveal do
18
+ RShade::Trace.reveal do
10
19
  #your code here
11
- end
12
- trace.show
13
- #rspec
14
- rshade_reveal do
15
- #code here
16
- end
20
+ end.show
21
+ ```
22
+ Due that tool create a detailed log with all of the steps of execution it's hard to read (it's can easelly be 20 - 30k lines because it's shows not only your custom code but also
23
+ code in gems that are involved in execution). `RShade` have filters to tackle this problem. Default filter is illiminate all code related to gems and only expose app code itself. You can
24
+ change this behaviour or add your own filter. Even when some piece of code are not shown due filtration order of calls and execution still shown in correct way.
25
+
26
+ ## Table of Contents
27
+ - [Configuration](#configuration)
28
+ - [Filters](#filters)
29
+ - [Filter by path include](#filter-by-path-include)
30
+ - [Filter by path exclude](#filter-by-path-exclude)
31
+ - [Filter by variable name or value](#filter-by-variable-name-or-value)
32
+ - [Examples](#examples)
33
+
34
+ ### Configuration
35
+ ```ruby
36
+ config = ::RShade::Config.default
37
+
38
+ RShade::Trace.reveal(config) do
39
+ end.show
40
+ ```
41
+ ### Filters
42
+ Filters by default represent by expression `(include_path or variable_match) or (not exclude_path)`
43
+ Filters can be chained like:
44
+ ```ruby
45
+ config = ::RShade::Config.default.include_paths { |paths| paths << /devise/ }
46
+ .exclude_paths { |paths| paths << /warden/ }
47
+ .match_variable { |name, value| name == :current_user }
48
+ ```
49
+ #### Filter by path include
50
+ `paths` - is array which accept regex or string
51
+ ```ruby
52
+ config = ::RShade::Config.default.include_paths { |paths| paths << /devise/ }
53
+
54
+ RShade::Trace.reveal(config) do
55
+ #your code
56
+ end.show
17
57
 
18
58
  ```
19
- ## Example
59
+ #### Filter by path exclude
60
+ `paths` - is array which accept regex or string
61
+ ```ruby
62
+ config = ::RShade::Config.default.exclude_paths { |paths| paths << /devise/ }
63
+
64
+ RShade::Trace.reveal(config) do
65
+ #your code
66
+ end.show
67
+ ```
68
+
69
+ #### Filter by variable name or value
70
+ `name` - represent variable name as symbol
71
+ `value` - actual variable value
72
+ ```ruby
73
+ config = ::RShade::Config.default.match_variable { |name, value| name == :current_user }
74
+
75
+ RShade::Trace.reveal(config) do
76
+ #your code
77
+ end.show
78
+ ```
79
+
80
+ ## Examples
20
81
  I've took example from https://github.com/spree/spree code base. Wrap code to take a look what code is in use when you save variant.
21
82
  On such huge codebase as spree it's helpful to know what callbacks are triggered and so on.
22
83
  ```ruby
@@ -25,7 +86,7 @@ On such huge codebase as spree it's helpful to know what callbacks are triggered
25
86
  before { variant.cost_currency = nil }
26
87
 
27
88
  it 'populates cost currency with the default value on save', focus: true do
28
- rshade_reveal do
89
+ RShade::Trace.reveal do
29
90
  variant.save!
30
91
  end
31
92
  expect(variant.cost_currency).to eql 'USD'
@@ -33,18 +94,11 @@ On such huge codebase as spree it's helpful to know what callbacks are triggered
33
94
  end
34
95
  end
35
96
  ```
97
+
36
98
  Below is example how output will look like.
37
99
  As you can see all code that have been in use is printed.
38
100
  [![asciicast](https://asciinema.org/a/MR5KL7TmHmYRUhwBUWQjBI373.svg)](https://asciinema.org/a/MR5KL7TmHmYRUhwBUWQjBI373)
39
101
 
40
- ## Installation
41
-
42
- Add this line to your application's Gemfile:
43
-
44
- ```ruby
45
- gem 'rshade'
46
- ```
47
-
48
102
  ## TODO
49
103
  Use stack to keep connections between current method and caller
50
104
  take a look on https://github.com/matugm/visual-call-graph
@@ -0,0 +1,72 @@
1
+ <!doctype html>
2
+
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>RShade call trace viewer</title>
7
+ <meta name="description" content="The HTML5 Herald">
8
+ <meta name="author" content="me">
9
+ <style>
10
+ body {
11
+ overflow-x: scroll;
12
+ }
13
+ .stacktrace-line {
14
+ color: #1c1c1c;
15
+ font-family: monospace;
16
+ border: 1px solid hsla(210,50%, 50%, 1);
17
+ margin-bottom: 10px;
18
+ width: fit-content;
19
+ }
20
+ .method-line {
21
+ font-size: 1.2rem;
22
+ padding: 10px;
23
+ background: hsla(210,50%, 50%, 0.1);
24
+ }
25
+
26
+ .path-line {
27
+ padding: 10px;
28
+ font-size: 0.9rem;
29
+ background: hsla(110,50%, 50%, 0.1);
30
+ }
31
+
32
+ .vars-line {
33
+ font-size: 0.7rem;
34
+ padding: 10px;
35
+ background: hsla(60,50%, 50%, 0.1);
36
+ }
37
+
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <div class="stacktrace"></div>
42
+ <script>
43
+ var data = <%=json %>;
44
+ var el = document.querySelector(".stacktrace");
45
+ for(i=0; i < data.length; i++) {
46
+ var value = data[i];
47
+ var newEl = document.createElement('div');
48
+ newEl.setAttribute("class", "stacktrace-line");
49
+ newEl.style.marginLeft = `${value.depth * 10}px`
50
+
51
+ line1 = document.createElement('div');
52
+ line1.setAttribute("class", "method-line");
53
+ line1.innerText = `${value.class}#${value.method_name}`;
54
+
55
+ var line2 = document.createElement('div');
56
+ line2.setAttribute("class", "path-line");
57
+ line2.innerText = `${value.class}#${value.full_path}`;
58
+
59
+ var line3 = document.createElement('div');
60
+ line3.setAttribute("class", "vars-line");
61
+ line3.innerText = JSON.stringify(value.vars)
62
+
63
+ newEl.appendChild(line1);
64
+ newEl.appendChild(line2);
65
+ newEl.appendChild(line3);
66
+ el.appendChild(newEl)
67
+ }
68
+ console.log(data);
69
+ </script>
70
+
71
+ </body>
72
+ </html>
@@ -0,0 +1,35 @@
1
+ require 'date'
2
+ module RShade
3
+ class BindingSerializer
4
+ SERIALIZE_CLASSES = [NilClass, TrueClass, FalseClass, Numeric, Time, Date, String, Symbol, Array]
5
+
6
+ def initialize(opts={})
7
+ end
8
+
9
+ def call(trace_binding)
10
+ vars = {}
11
+ trace_binding.each do |name, value|
12
+ if SERIALIZE_CLASSES.any? { |klass| value.is_a?(klass) }
13
+ vars[name] = value
14
+ elsif value.is_a?(Hash)
15
+ copy = shallow_copy_of_hash(value)
16
+ vars[name] = copy
17
+ else
18
+ class_name = value.is_a?(Class) ? value.to_s : value.class.to_s
19
+ vars[name] = class_name
20
+ end
21
+ end
22
+ vars
23
+ end
24
+
25
+ #TODO: work on more efficient way to serialize hash
26
+ def shallow_copy_of_hash(hash)
27
+ {}.tap do |new_hash|
28
+ hash.each do |k,v|
29
+ new_hash[k] = v.to_s
30
+ end
31
+ new_hash
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+
2
+ module RShade
3
+ class Config
4
+ class Store
5
+ attr_reader :filter_composition, :formatter, :tp_events
6
+
7
+ # @param [Hash] options
8
+ # @option options [RShade::Filter::FilterComposition] :filter_composition
9
+ # @option options [#call(event_store)] :formatter
10
+ # @option options [Array<Symbol>] :tp_events
11
+ def initialize(options={})
12
+ @filter_composition = options.fetch(:filter_composition, default_filter_composition)
13
+ @formatter = options.fetch(:formatter, ::RShade::Formatter::Stdout)
14
+ @tp_events = options.fetch(:tp_events, [:call, :return])
15
+ end
16
+
17
+ def set_tp_events(tp_events)
18
+ @tp_events = tp_events
19
+ end
20
+
21
+ def config_filter(filter_type, &block)
22
+ filter_composition.config_filter(filter_type, &block)
23
+ self
24
+ end
25
+
26
+ def set_formatter(formatter)
27
+ @formatter = formatter
28
+ self
29
+ end
30
+
31
+ private
32
+
33
+ def default_filter_composition
34
+ RShade::Filter::FilterBuilder.build([:or,[:or, RShade::Filter::VariableFilter.new, RShade::Filter::IncludePathFilter.new] , RShade::Filter::ExcludePathFilter.new])
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,71 @@
1
+ module RShade
2
+ class Config
3
+ RUBY_VERSION_PATTERN = /ruby-[0-9.]*/
4
+
5
+ def self.default
6
+ ::RShade::Config::Store.new.set_formatter(::RShade::Formatter::Stdout.new)
7
+ .config_filter(::RShade::Filter::ExcludePathFilter) do |paths|
8
+ default_excluded_path.each do |path|
9
+ paths << path
10
+ end
11
+ end
12
+ end
13
+
14
+ # @param [RShade::Config::Store] config_store
15
+ def initialize(config_store)
16
+ @config_store = config_store
17
+ end
18
+
19
+ # @param [Hash] options
20
+ # @option options [RShade::Filter::FilterComposition] :filter_composition
21
+ # @option options [#call(event_store)] :formatter
22
+ # @option options [Array<Symbol>] :tp_events
23
+ def self.create(options={})
24
+ new(Config::Store.new(options))
25
+ end
26
+
27
+ def tp_events(&block)
28
+ events = block.call
29
+ @config_store.set_tp_events(events)
30
+ self
31
+ end
32
+
33
+ def include_paths(&block)
34
+ @config_store.config_filter(::RShade::Filter::IncludePathFilter, &block)
35
+ self
36
+ end
37
+
38
+ def exclude_paths(&block)
39
+ @config_store.config_filter(::RShade::Filter::ExcludePathFilter, &block)
40
+ self
41
+ end
42
+
43
+ def match_variable(&block)
44
+ @config_store.config_filter(::RShade::Filter::VariableFilter, &block)
45
+ self
46
+ end
47
+
48
+ def formatter(&block)
49
+ formatter = block.call
50
+ @config_store.set_formatter(formatter)
51
+ self
52
+ end
53
+
54
+ def value
55
+ @config_store
56
+ end
57
+
58
+ def self.store_dir
59
+ File.expand_path('../../tmp', __dir__)
60
+ end
61
+
62
+ def self.root_dir
63
+ @root_dir ||= File.expand_path('../../', __dir__)
64
+ end
65
+
66
+ private
67
+ def self.default_excluded_path
68
+ [ENV['GEM_PATH'].split(':'), RUBY_VERSION_PATTERN, /internal/].flatten.compact
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,8 @@
1
+ class Object
2
+ def reveal(&block)
3
+ trace = ::RShade::Trace.reveal do
4
+ block.call
5
+ end
6
+ trace.show
7
+ end
8
+ end
@@ -0,0 +1,60 @@
1
+ module RShade
2
+ # nodoc
3
+ class Event
4
+ attr_reader :hash, :skipped
5
+ RETURN_EVENTS = [:return, :b_return, :c_return]
6
+
7
+
8
+ def initialize(hash)
9
+ @hash = hash
10
+ end
11
+
12
+ [:klass, :path, :lineno, :method_name, :vars, :level, :return_value].each do |method_name|
13
+ define_method method_name do
14
+ fetch method_name
15
+ end
16
+ end
17
+
18
+ def with_level!(level)
19
+ @hash[:level] = level
20
+ self
21
+ end
22
+
23
+ def set_return_value!(return_value)
24
+ @hash[:return_value] = return_value
25
+ self
26
+ end
27
+
28
+ def with_serialized_return!(serializer)
29
+ @hash[:return_value] = serializer.call(@hash[:return_value])
30
+ self
31
+ end
32
+
33
+ def with_serialized_vars!(serializer)
34
+ @hash[:vars] = serializer.call(@hash[:vars])
35
+ self
36
+ end
37
+
38
+ def self.from_trace_point(evt)
39
+ vars = {}
40
+ evt.binding.local_variables.each do |var_name|
41
+ vars[var_name] = evt.binding.local_variable_get var_name
42
+ end
43
+
44
+ hash = { path: evt.path, lineno: evt.lineno, klass: evt.defined_class, method_name: evt.method_id, vars: vars,
45
+ event_type: evt.event }
46
+ hash.merge!({return_value: evt.return_value}) if RETURN_EVENTS.include?(evt.event)
47
+ new(hash)
48
+ end
49
+
50
+ def self.create_blank(level)
51
+ new({level: level}, true)
52
+ end
53
+
54
+ private
55
+
56
+ def fetch(key)
57
+ @hash[key]
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,45 @@
1
+ module RShade
2
+ class EventObserver
3
+ attr_reader :event_processor, :config
4
+
5
+ # @param [RShade::Config::Store] config
6
+ # @param [RShade::EventProcessor] event_store
7
+ def initialize(config, event_processor)
8
+ @event_processor = event_processor
9
+ @config = config
10
+ @level = 0
11
+ @hook = Hash.new(0)
12
+ @hook[:enter] = 1
13
+ @hook[:leave] = -1
14
+ end
15
+
16
+ # @param [:enter, :leave, :other] type
17
+ # @param [RShade::Event] event
18
+ def call(event, type)
19
+ @level += @hook[type]
20
+ return unless pass?(event)
21
+
22
+ enter(event) if type == :enter
23
+ leave(event) if type == :leave
24
+ other(event) if type == :other
25
+ end
26
+
27
+ private
28
+
29
+ def enter(event)
30
+ event_processor.enter event, @level
31
+ end
32
+
33
+ def leave(event)
34
+ event_processor.leave event, @level
35
+ end
36
+
37
+ def other(event)
38
+ event_processor.other event, @level
39
+ end
40
+
41
+ def pass?(event)
42
+ config.filter_composition.call(event)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ module RShade
2
+ # nodoc
3
+ class EventProcessor
4
+ attr_reader :store
5
+
6
+ def initialize(store)
7
+ @store = store
8
+ @var_serializer = BindingSerializer.new
9
+ end
10
+
11
+ # @param [RShade::Event] event
12
+ # @param [Integer] level
13
+ def enter(event, level)
14
+ event.with_serialized_vars!(@var_serializer).with_level!(level)
15
+ store.add(event, level)
16
+ end
17
+
18
+ # @param [RShade::Event] event
19
+ # @param [Integer] level
20
+ def leave(event, level)
21
+ store.current! do |node|
22
+ node.value.set_return_value!(event.return_value)
23
+ .with_serialized_return!(->(value) { value.inspect })
24
+ end
25
+ rescue => e
26
+ # this rescue here due this issue which reproduce in ruby-2.6.6 at least
27
+ # https://bugs.ruby-lang.org/issues/18060
28
+ end
29
+ # @param [RShade::Event] event
30
+ # @param [Integer] level
31
+ def other(event, level)
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,75 @@
1
+ module RShade
2
+ class EventTree
3
+ include Enumerable
4
+
5
+ attr_reader :current, :head
6
+
7
+ def initialize
8
+ @current = @head = EventTreeNode.new(nil, 0, nil)
9
+ end
10
+
11
+ def add(value, level)
12
+ if current.level + 1 == level
13
+ current.children << EventTreeNode.new(value, level, current)
14
+ return
15
+ end
16
+ if current.level + 1 < level
17
+
18
+ last = current.children.last
19
+ unless last
20
+ current.children << EventTreeNode.new(nil, current.level + 1 , current)
21
+ end
22
+ @current = current.children.last
23
+ add(value, level)
24
+ return
25
+ end
26
+
27
+ if current.level + 1 > level
28
+ unless current.parent
29
+ return
30
+ end
31
+ @current = current.parent
32
+ add(value, level)
33
+ end
34
+ end
35
+
36
+ def current!(&block)
37
+ block.call(current.children.last) if current.children.last
38
+ end
39
+
40
+ def each(&block)
41
+ @head.each(&block)
42
+ end
43
+ end
44
+
45
+ class EventTreeNode
46
+ include Enumerable
47
+ attr_reader :children, :level, :vlevel, :value
48
+ attr_accessor :parent
49
+
50
+ def initialize(value, level, parent=nil)
51
+ @children = []
52
+ @level = level
53
+ @parent = parent
54
+ @value = value
55
+ @vlevel = set_vlevel(parent)
56
+ end
57
+
58
+ def each(&block)
59
+ block.call(self) if parent != nil || value != nil
60
+ children.each { |item| item.each(&block) }
61
+ end
62
+
63
+ private
64
+
65
+ def set_vlevel(node)
66
+ return 0 if node == nil
67
+
68
+ if node.value || node.level == 0
69
+ node.vlevel + 1
70
+ else
71
+ set_vlevel(node.parent)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,26 @@
1
+ module RShade
2
+ module Filter
3
+ class AbstractFilter
4
+ def name
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def priority
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def call(event)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def config_call
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def config(&block)
21
+ config_call(&block)
22
+ self
23
+ end
24
+ end
25
+ end
26
+ end