rshade 0.1.8 → 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: f10cfe61e925b1882ff42ea7580a2a6d72b5eadd4f18831a7e52813e22d1daea
4
- data.tar.gz: cfe32fccff6bd61114bb86d0fd0d790d66c61ca41ce7bda0fc39e03af73ddb46
3
+ metadata.gz: e5ef0c31c59c3ed46f44326e1e28f6a5b2b2ccbdbce32152e6b54233f875d6d0
4
+ data.tar.gz: f394a28db1cda3274954309996eaea895bf715737f34aecbb09c0d0873fd09fb
5
5
  SHA512:
6
- metadata.gz: 34222d8c0982a0af1ccbd37a2436f3dfa58d164ad4614a51db4c2074aff55f5adc873aa89008b173a065ec83975b3e67f9266437931efc1725baf50d04638bd5
7
- data.tar.gz: 231f085edc963e287f1e8ae1b0843567ff5857cbfd50d80e240548e143148609c44d02d76204a6dd021765785c7ce347e6d7dcdeb49a388ff08197ce52074f45
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.7)
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 (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,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
data/lib/rshade/event.rb CHANGED
@@ -1,35 +1,60 @@
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
16
+ end
17
+
18
+ def with_level!(level)
19
+ @hash[:level] = level
20
+ self
13
21
  end
14
22
 
15
- def klass
16
- @hash[:klass]
23
+ def set_return_value!(return_value)
24
+ @hash[:return_value] = return_value
25
+ self
17
26
  end
18
27
 
19
- def path
20
- @hash[:path]
28
+ def with_serialized_return!(serializer)
29
+ @hash[:return_value] = serializer.call(@hash[:return_value])
30
+ self
21
31
  end
22
32
 
23
- def lineno
24
- @hash[:lineno]
33
+ def with_serialized_vars!(serializer)
34
+ @hash[:vars] = serializer.call(@hash[:vars])
35
+ self
25
36
  end
26
37
 
27
- def method_name
28
- @hash[:method_name]
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)
29
48
  end
30
49
 
31
- def vars
32
- @hash[:vars]
50
+ def self.create_blank(level)
51
+ new({level: level}, true)
52
+ end
53
+
54
+ private
55
+
56
+ def fetch(key)
57
+ @hash[key]
33
58
  end
34
59
  end
35
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
@@ -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