peeek 1.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1d9163af655c3a81a95497b5b85dbba4806d38a8
4
+ data.tar.gz: 94d66998484a454134c8c41041e593f4b458b458
5
+ SHA512:
6
+ metadata.gz: e4593100bd8419742be0174883536e40f7628211411f124259eef776481968486a387a2916dc958510239a7be27b266ce004f094a8b5fc87d059095d435f51cf
7
+ data.tar.gz: 2263f4beddadacbc497b4cc1c47a076b34c755c8ef9e8cda003b7e8ad55d7eef423fdbd427bfe014a5314bbf456feb905fd52e5fb49e76d894162221e2fc7ae6
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hoook.gemspec
4
+ gemspec
5
+
6
+ gem 'ruby18_source_location', :group => :development, :platforms => :ruby_18
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Takahiro Kondo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ Peeek
2
+ =====
3
+
4
+ Peeek peeks at calls of a method
5
+
6
+ Installation
7
+ ------------
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'peeek'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install peeek
20
+
21
+ Usage
22
+ -----
23
+
24
+ You can peek at calls of a method by `Object#peeek` (or `Peeek#hook`).
25
+
26
+ require 'peeek'
27
+
28
+ String.peeek(:%) # or Peeek.current.hook(String, :%)
29
+
30
+ format = '%s (%d)'
31
+ puts format % ['Koyomi', 18]
32
+ puts format % ['Karen', 14]
33
+ puts format % ['Tsukihi', 14]
34
+
35
+ puts Peeek.current.calls # => String#% from "%s (%d)" with ["Koyomi", 18] returned "Koyomi (18)" in peeek-example.rb at 6
36
+ # => String#% from "%s (%d)" with ["Karen", 14] returned "Karen (14)" in peeek-example.rb at 7
37
+ # => String#% from "%s (%d)" with ["Tsukihi", 14] returned "Tsukihi (14)" in peeek-example.rb at 8
38
+
39
+ ### How to hook to a method
40
+
41
+ Call `Object#peeek` to any module or any class, hook to their instance methods.
42
+
43
+ String.peeek(:%)
44
+
45
+ Also for an instance of any class, hook to its singleton methods.
46
+
47
+ $stdout.peeek(:write)
48
+
49
+ If want to choose whether to hook to either instance method or singleton method,
50
+ add "#" before in the method name in instance method, add "." in singleton
51
+ method.
52
+
53
+ String.peeek('#%')
54
+ $stdout.peeek('.write')
55
+ Regexp.peeek('.quote')
56
+
57
+ Even if the method isn't defined at the time you call `Object#peeek`, enable the
58
+ hook when the method is defined.
59
+
60
+ Kernel.peeek(:pp)
61
+ require 'pp' # enable the hook
62
+ pp {'Koyomi' => 18, 'Karen' => 14, 'Tsukihi' => 14}
63
+
64
+ ### Localize a Peeek object
65
+
66
+ `Peeek.local` enables hooks only in a block.
67
+
68
+ require 'peeek'
69
+
70
+ format = '%s (%d)'
71
+
72
+ calls = Peeek.local do
73
+ String.peeek(:%)
74
+ puts format % ['Koyomi', 18]
75
+ Peeek.current.calls
76
+ end
77
+
78
+ puts calls # => String#% from "%s (%d)" with ["Koyomi", 18] returned "Koyomi (18)" in peeek-local.rb at 7
79
+
80
+ puts format % ['Karen', 14] # not captured
81
+
82
+ Hook to methods at head of the block, and return the calls, then use
83
+ `Peeek.capture`.
84
+
85
+ require 'peeek'
86
+
87
+ format = '%s (%d)'
88
+
89
+ calls = Peeek.capture(String => :%) do
90
+ puts format % ['Koyomi', 18]
91
+ end
92
+
93
+ puts calls # => String#% from "%s (%d)" with ["Koyomi", 18] returned "Koyomi (18)" in peeek-capture.rb at 6
94
+
95
+ puts format % ['Karen', 14] # not captured
96
+
97
+ ### Filter calls
98
+
99
+ `Peeek#calls` or `Peeek.local` return an instance of `Peeek::Calls`. It has
100
+ implemented methods to filter by attributes of the call.
101
+
102
+ require 'peeek'
103
+
104
+ format = '%s (%d)'
105
+
106
+ calls = Peeek.capture(String => :%) do
107
+ puts format % ['Koyomi', 18]
108
+ puts format % ['Karen'] rescue $!
109
+ end
110
+
111
+ puts calls.in('peeek-calls.rb') # filter by file name
112
+ puts calls.in(/\.rb/) # can also use regular expressions
113
+ puts calls.at(6) # filter by line number
114
+ puts calls.at(1..10) # can also use range
115
+ puts calls.from('%s (%d)') # filter by receiver
116
+ puts calls.return_values # filter only calls that returned a value
117
+ puts calls.exceptions # filter only calls that raised an exception
118
+
119
+
120
+ Contributing
121
+ ------------
122
+
123
+ 1. Fork it
124
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
125
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
126
+ 4. Push to the branch (`git push origin my-new-feature`)
127
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/peeek ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ require 'stringio'
3
+ require 'peeek'
4
+
5
+ path = ARGV.first
6
+
7
+ original_stdout = $stdout
8
+ $stdout = StringIO.new
9
+
10
+ calls = begin
11
+ Peeek.capture(String => :%) { load path } # want to be you can specify
12
+ ensure
13
+ $stdout = original_stdout
14
+ end
15
+
16
+ puts calls
data/lib/peeek.rb ADDED
@@ -0,0 +1,147 @@
1
+ require 'peeek/version'
2
+ require 'peeek/hook'
3
+ require 'peeek/hooks'
4
+ require 'peeek/supervisor'
5
+ require 'peeek/calls'
6
+
7
+ class Peeek
8
+
9
+ # @attribute [r] global
10
+ # @return [Peeek] the global Peeek object
11
+ def self.global
12
+ @global ||= new
13
+ end
14
+
15
+ # @attribute [r] current
16
+ # @return [Peeek] the current Peeek object
17
+ #
18
+ # @see Peeek.local
19
+ def self.current
20
+ @current ||= global
21
+ end
22
+
23
+ # Run process to switch to a local Peeek object from the current Peeek
24
+ # object. The local Peeek object doesn't inherit the registered hooks and
25
+ # supervision from the current Peeek object. The current Peeek object reverts
26
+ # after ran the process.
27
+ #
28
+ # @yield any process that want to run to switch
29
+ def self.local
30
+ raise ArgumentError, 'block not supplied' unless block_given?
31
+
32
+ old = current
33
+ @current = new
34
+
35
+ old.circumvent do
36
+ begin
37
+ yield
38
+ ensure
39
+ current.release
40
+ @current = old
41
+ end
42
+ end
43
+ end
44
+
45
+ # Capture all calls to hook targets.
46
+ #
47
+ # @param [Hash{Module, Class, Object => String, Array<String>, Symbol, Array<Symbol>}]
48
+ # object_and_method_specs an object and method specification(s) that be
49
+ # target of hook
50
+ # @yield any process that want to run to capture
51
+ # @return [Peeek::Calls] calls that were captured in the block
52
+ def self.capture(object_and_method_specs)
53
+ raise ArgumentError, 'block not supplied' unless block_given?
54
+
55
+ local do
56
+ object_and_method_specs.each { |object, method_specs| current.hook(object, *method_specs) }
57
+ yield
58
+ current.calls
59
+ end
60
+ end
61
+
62
+ # Initialize the Peeek object.
63
+ def initialize
64
+ @hooks = Hooks.new
65
+ @instance_supervisor = Supervisor.create_for_instance
66
+ @singleton_supervisor = Supervisor.create_for_singleton
67
+ end
68
+
69
+ # @attribute [r] hooks
70
+ # @return [Peeek::Hooks] the registered hooks
71
+ attr_reader :hooks
72
+
73
+ # @attribute [r] calls
74
+ # @return [Peeek::Calls] calls to the methods that the registered hooks
75
+ # captured
76
+ def calls
77
+ Calls.new(@hooks.map(&:calls).inject([], &:+))
78
+ end
79
+
80
+ # Register a hook to methods of an object.
81
+ #
82
+ # @param [Module, Class, Object] object a target object that hook
83
+ # @param [Array<String>, Array<Symbol>] method_specs method specifications of
84
+ # the object. see also examples of {Peeek::Hook.create}
85
+ # @yield [call] process a call to the methods. give optionally
86
+ # @yieldparam [Peeek::Call] call a call to the methods
87
+ # @return [Peeek::Hooks] registered hooks at calling
88
+ #
89
+ # @see Peeek::Hook.create
90
+ def hook(object, *method_specs, &process)
91
+ hooks = method_specs.map do |method_spec|
92
+ Hook.create(object, method_spec, &process).tap do |hook|
93
+ if hook.defined?
94
+ hook.link
95
+ elsif hook.instance?
96
+ @instance_supervisor << hook
97
+ elsif hook.singleton?
98
+ @singleton_supervisor << hook
99
+ end
100
+ end
101
+ end
102
+
103
+ @hooks.push(*hooks)
104
+ Hooks.new(hooks)
105
+ end
106
+
107
+ # Release the registered hooks and supervision.
108
+ def release
109
+ @hooks.clear
110
+ @instance_supervisor.clear
111
+ @singleton_supervisor.clear
112
+ self
113
+ end
114
+
115
+ # Run process while circumvent the registered hooks and supervision.
116
+ #
117
+ # @yield any process that want to run while circumvent the registered hooks
118
+ # and supervision
119
+ def circumvent(&process)
120
+ raise ArgumentError, 'block not supplied' unless block_given?
121
+
122
+ @singleton_supervisor.circumvent do
123
+ @instance_supervisor.circumvent do
124
+ @hooks.circumvent(&process)
125
+ end
126
+ end
127
+ end
128
+
129
+ module Readily
130
+
131
+ # Register a hook to methods of self to the current Peeek object.
132
+ #
133
+ # @param [Array<String>, Array<Symbol>] method_specs method specifications
134
+ # of the object. see also examples of {Peeek::Hook.create}
135
+ # @yield [call] process a call to the methods. give optionally
136
+ # @yieldparam [Peeek::Call] call a call to the methods
137
+ #
138
+ # @see Peeek#hook
139
+ def peeek(*method_specs, &process)
140
+ Peeek.current.hook(self, *method_specs, &process)
141
+ end
142
+
143
+ end
144
+
145
+ Object.__send__(:include, Readily)
146
+
147
+ end
data/lib/peeek/call.rb ADDED
@@ -0,0 +1,134 @@
1
+ class Peeek
2
+ class Call
3
+
4
+ # Initialize the call.
5
+ #
6
+ # @param [Peeek::Hook] hook hook the call occurred
7
+ # @param [Array<String>] backtrace backtrace the call occurred
8
+ # @param [Module, Class, Object] receiver object that received the call
9
+ # @param [Array] arguments arguments at the call
10
+ # @param [Peeek::Call::Result] result result of the call
11
+ def initialize(hook, backtrace, receiver, arguments, result)
12
+ raise ArgumentError, 'invalid as result' unless result.is_a?(Result)
13
+ @hook = hook
14
+ @backtrace = backtrace
15
+ @file, @line = extract_file_and_line(backtrace.first)
16
+ @receiver = receiver
17
+ @arguments = arguments
18
+ @result = result
19
+ end
20
+
21
+ # @attribute [r] hook
22
+ # @return [Peeek::Hook] hook the call occurred
23
+ attr_reader :hook
24
+
25
+ # @attribute [r] backtrace
26
+ # @return [Array<String>] backtrace the call occurred
27
+ attr_reader :backtrace
28
+
29
+ # @attribute [r] file
30
+ # @return [String] name of file the call occurred
31
+ attr_reader :file
32
+
33
+ # @attribute [r] line
34
+ # @return [Integer] line number the call occurred
35
+ attr_reader :line
36
+
37
+ # @attribute [r] receiver
38
+ # @return [Module, Class, Object] object that received the call
39
+ attr_reader :receiver
40
+
41
+ # @attribute [r] arguments
42
+ # @return [Array] arguments at the call
43
+ attr_reader :arguments
44
+
45
+ # @attribute [r] result
46
+ # @return [Peeek::Call::Result] result of the call
47
+ attr_reader :result
48
+
49
+ # @attribute [r] return_value
50
+ # @return [Object] value that the call returned
51
+ def return_value
52
+ raise TypeError, "the call didn't return a value" unless returned?
53
+ @result.value
54
+ end
55
+
56
+ # @attribute [r] exception
57
+ # @return [StandardError] exception that raised from the call
58
+ def exception
59
+ raise TypeError, "the call didn't raised an exception" unless raised?
60
+ @result.value
61
+ end
62
+
63
+ # Determine if the result is a return value.
64
+ #
65
+ # @return whether the result is a return value
66
+ def returned?
67
+ @result.is_a?(ReturnValue)
68
+ end
69
+
70
+ # Determine if the result is an exception.
71
+ #
72
+ # @return whether the result is an exception
73
+ def raised?
74
+ @result.is_a?(Exception)
75
+ end
76
+
77
+ def to_s
78
+ parts = [@hook.to_s]
79
+ parts << "from #{@receiver.inspect}"
80
+
81
+ if @arguments.size == 1
82
+ parts << "with #{@arguments.first.inspect}"
83
+ elsif @arguments.size > 1
84
+ parts << "with (#{@arguments.map(&:inspect) * ', '})"
85
+ end
86
+
87
+ if returned?
88
+ parts << "returned #{return_value.inspect}"
89
+ elsif raised?
90
+ parts << "raised #{exception.inspect}"
91
+ end
92
+
93
+ parts << "in #{@file}"
94
+ parts << "at #{@line}"
95
+ parts * ' '
96
+ end
97
+
98
+ private
99
+
100
+ def extract_file_and_line(string)
101
+ _, file, line = /^(.+):(\d+)(?::in\s+|$)/.match(string).to_a
102
+ raise ArgumentError, 'invalid as string of backtrace' unless file and line
103
+ [file, line.to_i]
104
+ end
105
+
106
+ class Result
107
+
108
+ # Initialize the result.
109
+ #
110
+ # @param [Object] value value of the result
111
+ def initialize(value)
112
+ @value = value
113
+ end
114
+
115
+ # @attribute [r] value
116
+ # @return [Object] value of the result
117
+ attr_reader :value
118
+
119
+ def ==(other)
120
+ self.class == other.class && @value == other.value
121
+ end
122
+ alias eql? ==
123
+
124
+ def hash
125
+ (self.class.hash << 32) + @value.hash
126
+ end
127
+
128
+ end
129
+
130
+ class ReturnValue < Result; end
131
+ class Exception < Result; end
132
+
133
+ end
134
+ end