rshade 0.1.7 → 0.1.9.1

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: 5594d3ba4348f6f4c4b73a74c223a7b30f3b7eda7216c72d0d122079cae63bf7
4
- data.tar.gz: b071916008465bed00ff107b8b88fe778235a333e1a24c74af5ea62631ec4b75
3
+ metadata.gz: 91ab4d11c9af8066bff925ab208f10e20c5bf7fd5685418b0c8ef01880c13d2f
4
+ data.tar.gz: 7c0e95c99b1707e5290440cf3459e02cd465c653bdbfbfdc3f216d77a3647c66
5
5
  SHA512:
6
- metadata.gz: b4a81694ecce4e7063a8a8d4cd5ed758ea93ca151aa0b09cd785e281d206c4cdcdfa389237523fe05f57441c6f4ad648f5cd60e1abb46793162b2ff182965555
7
- data.tar.gz: 756a618370faa424864563640ac0389124201bc5c49eb47fdb6cae0fe8f535dadc7c97c366b6c5dfaa330700a39d9e2eaa6e9caad9538209319a7da9587c142f
6
+ metadata.gz: 21fe1fa50bf9c63261fbed6651ad8999edc22734567c3f33b2ba872ba95640cb70b3cff8fa0c640fd13faa8cf73371bb107ee8d0303e3e1322bba06a87f30d58
7
+ data.tar.gz: 2b657588399858bcc1aa9f60cac2baf92ea3427b341542c40f15c7a7a26cbfb17cabfa3b98a2c5465c1905a3c8dfeb9500ed772351d89bdd7c2b6ca3ac223392
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.1)
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 (13.0.1)
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
39
  rake (>= 12.3.3)
39
40
  rshade!
40
41
  rspec (~> 3.0)
41
42
 
42
43
  BUNDLED WITH
43
- 1.17.3
44
+ 2.2.33
data/README.md CHANGED
@@ -2,20 +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.reveal do
18
+ RShade::Trace.reveal do
9
19
  #your code here
10
- end
11
- trace.show
12
- #rspec
13
- rshade_reveal do
14
- #code here
15
- 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
16
57
 
17
58
  ```
18
- ## 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
19
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.
20
82
  On such huge codebase as spree it's helpful to know what callbacks are triggered and so on.
21
83
  ```ruby
@@ -24,7 +86,7 @@ On such huge codebase as spree it's helpful to know what callbacks are triggered
24
86
  before { variant.cost_currency = nil }
25
87
 
26
88
  it 'populates cost currency with the default value on save', focus: true do
27
- rshade_reveal do
89
+ RShade::Trace.reveal do
28
90
  variant.save!
29
91
  end
30
92
  expect(variant.cost_currency).to eql 'USD'
@@ -32,18 +94,11 @@ On such huge codebase as spree it's helpful to know what callbacks are triggered
32
94
  end
33
95
  end
34
96
  ```
97
+
35
98
  Below is example how output will look like.
36
99
  As you can see all code that have been in use is printed.
37
100
  [![asciicast](https://asciinema.org/a/MR5KL7TmHmYRUhwBUWQjBI373.svg)](https://asciinema.org/a/MR5KL7TmHmYRUhwBUWQjBI373)
38
101
 
39
- ## Installation
40
-
41
- Add this line to your application's Gemfile:
42
-
43
- ```ruby
44
- gem 'rshade'
45
- ```
46
-
47
102
  ## TODO
48
103
  Use stack to keep connections between current method and caller
49
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,46 @@
1
+
2
+ module RShade
3
+ class Config
4
+ class Store
5
+ attr_reader :filter_composition, :formatter, :tp_events, :custom_serializers
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
+ @custom_serializers = options.fetch(:custom_serializers, {})
16
+ end
17
+
18
+ def set_tp_events(tp_events)
19
+ @tp_events = tp_events
20
+ self
21
+ end
22
+
23
+ def add_custom_serializers(hash)
24
+ custom_serializers.merge!(hash)
25
+ self
26
+ end
27
+
28
+
29
+ def config_filter(filter_type, &block)
30
+ filter_composition.config_filter(filter_type, &block)
31
+ self
32
+ end
33
+
34
+ def set_formatter(formatter)
35
+ @formatter = formatter
36
+ self
37
+ end
38
+
39
+ private
40
+
41
+ def default_filter_composition
42
+ RShade::Filter::FilterBuilder.build([:or,[:or, RShade::Filter::VariableFilter.new, RShade::Filter::IncludePathFilter.new] , RShade::Filter::ExcludePathFilter.new])
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,76 @@
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 add_custom_serializers(hash)
55
+ @config_store.add_custom_serializers(hash)
56
+ self
57
+ end
58
+
59
+ def value
60
+ @config_store
61
+ end
62
+
63
+ def self.store_dir
64
+ File.expand_path('../../tmp', __dir__)
65
+ end
66
+
67
+ def self.root_dir
68
+ @root_dir ||= File.expand_path('../../', __dir__)
69
+ end
70
+
71
+ private
72
+ def self.default_excluded_path
73
+ [ENV['GEM_PATH'].split(':'), RUBY_VERSION_PATTERN, /internal/].flatten.compact
74
+ end
75
+ end
76
+ 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
data/lib/rshade/event.rb CHANGED
@@ -1,35 +1,56 @@
1
1
  module RShade
2
2
  # nodoc
3
3
  class Event
4
- attr_reader :hash
5
- attr_accessor :depth
4
+ attr_reader :hash, :skipped
5
+ RETURN_EVENTS = [:return, :b_return, :c_return]
6
+
6
7
 
7
8
  def initialize(hash)
8
9
  @hash = hash
9
10
  end
10
11
 
11
- def level
12
- @hash[:level]
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
13
16
  end
14
17
 
15
- def klass
16
- @hash[:klass]
18
+ def with_level!(level)
19
+ @hash[:level] = level
20
+ self
17
21
  end
18
22
 
19
- def path
20
- @hash[:path]
23
+ def set_return_value!(return_value)
24
+ @hash[:return_value] = return_value
25
+ self
21
26
  end
22
27
 
23
- def lineno
24
- @hash[:lineno]
28
+ def with_serialized_return!(serializer)
29
+ @hash[:return_value] = serializer.call(@hash[:return_value])
30
+ self
25
31
  end
26
32
 
27
- def method_name
28
- @hash[:method_name]
33
+ def with_serialized_vars!(serializer)
34
+ @hash[:vars] = serializer.call(@hash[:vars])
35
+ self
29
36
  end
30
37
 
31
- def vars
32
- @hash[:vars]
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
+ private
51
+
52
+ def fetch(key)
53
+ @hash[key]
33
54
  end
34
55
  end
35
56
  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,36 @@
1
+ module RShade
2
+ # nodoc
3
+ class EventProcessor
4
+ attr_reader :store
5
+
6
+ def initialize(store, config)
7
+ @store = store
8
+ custom_serializers = config.custom_serializers
9
+ @var_serializer = ::RShade::Serializer::Traversal.new(custom_serializers)
10
+ end
11
+
12
+ # @param [RShade::Event] event
13
+ # @param [Integer] level
14
+ def enter(event, level)
15
+ event.with_serialized_vars!(@var_serializer).with_level!(level)
16
+ store.add(event, level)
17
+ end
18
+
19
+ # @param [RShade::Event] event
20
+ # @param [Integer] level
21
+ def leave(event, level)
22
+ store.current! do |node|
23
+ node.value.set_return_value!(event.return_value)
24
+ .with_serialized_return!(->(value) { value.inspect })
25
+ end
26
+ rescue => e
27
+ # this rescue here due this issue which reproduce in ruby-2.6.6 at least
28
+ # https://bugs.ruby-lang.org/issues/18060
29
+ end
30
+ # @param [RShade::Event] event
31
+ # @param [Integer] level
32
+ def other(event, level)
33
+
34
+ end
35
+ end
36
+ 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