irwebmachine 0.1.0
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.
- data/.gitignore +17 -0
- data/.pryrc +5 -0
- data/.travis.yml +8 -0
- data/.yardopts +2 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +98 -0
- data/Rakefile +17 -0
- data/irwebmachine.gemspec +19 -0
- data/lib/irwebmachine/application.rb +41 -0
- data/lib/irwebmachine/frame.rb +26 -0
- data/lib/irwebmachine/irb/bundle.rb +20 -0
- data/lib/irwebmachine/irb.rb +8 -0
- data/lib/irwebmachine/pry/enter_stack.rb +58 -0
- data/lib/irwebmachine/pry/nav.rb +14 -0
- data/lib/irwebmachine/pry/print_stack.rb +42 -0
- data/lib/irwebmachine/pry.rb +10 -0
- data/lib/irwebmachine/stack.rb +58 -0
- data/lib/irwebmachine/traced_request.rb +55 -0
- data/lib/irwebmachine/tracer.rb +167 -0
- data/lib/irwebmachine/version.rb +3 -0
- data/lib/irwebmachine.rb +17 -0
- data/test/fixtures/resource.rb +38 -0
- data/test/irwebmachine_application_test.rb +50 -0
- data/test/setup.rb +8 -0
- metadata +98 -0
data/.gitignore
ADDED
data/.pryrc
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Robert Gleeson
|
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,98 @@
|
|
1
|
+
__OVERVIEW__
|
2
|
+
|
3
|
+
|
4
|
+
| Project | IRWebmachine
|
5
|
+
|:----------------|:--------------------------------------------------
|
6
|
+
| Homepage | https://github.com/robgleeson/irwebmachine
|
7
|
+
| Documentation | http://rubydoc.info/gems/irwebmachine/frames
|
8
|
+
| Author | Robert Gleeson
|
9
|
+
|
10
|
+
__DESCRIPTION__
|
11
|
+
|
12
|
+
IRWebmachine is an interactive debugger you can use to make, trace & debug
|
13
|
+
HTTP requests destined for a [webmachine-ruby](https://github.com/seancribbs/webmachine-ruby)
|
14
|
+
resource. IRwebmachine is designed to be used inside a REPL such as Pry, but
|
15
|
+
there is very basic IRB support as well. I recommend Pry for now, as IRB support
|
16
|
+
is unfinished & Pry offers some cool features for free.
|
17
|
+
|
18
|
+
__CONFIGURATION__
|
19
|
+
|
20
|
+
Copy the ruby code below into a project-local .pryrc file.
|
21
|
+
'MyApp' can be an instance of Webmachine::Application, or a subclass of
|
22
|
+
Webmachine::Resource. It is used by irwebmachine when dispatching requests and
|
23
|
+
it can be changed to another application or resource while in the REPL.
|
24
|
+
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'myapp'
|
28
|
+
require 'irwebmachine/pry'
|
29
|
+
IRWebmachine.app = MyApp
|
30
|
+
```
|
31
|
+
|
32
|
+
__USAGE__
|
33
|
+
|
34
|
+
- Making requests
|
35
|
+
|
36
|
+
The first thing you'll probably want to do is make a request to a resource.
|
37
|
+
GET, POST, DELETE & PUT requests can be made through the 'app' method.
|
38
|
+
The example illustrates how to make a GET request:
|
39
|
+
|
40
|
+
[1] pry(main)> app.get "/"
|
41
|
+
=> #<Webmachine::Response:0x007faa44143980
|
42
|
+
@body="GET OK",
|
43
|
+
@code=200,
|
44
|
+
@headers=
|
45
|
+
{"Content-Type"=>"plain/text",
|
46
|
+
"Content-Length"=>"6",
|
47
|
+
"X-Request-Query"=>{},
|
48
|
+
"X-Request-Headers"=>{}},
|
49
|
+
@redirect=false,
|
50
|
+
@trace=[]
|
51
|
+
>
|
52
|
+
|
53
|
+
- Tracing
|
54
|
+
|
55
|
+
After you have made a request you can visualize what methods were called(and
|
56
|
+
in what order) through the `print-stack` command. The output can be filtered,
|
57
|
+
see `print-stack -h` for more information.
|
58
|
+
|
59
|
+
[2] pry(main)> print-stack
|
60
|
+
0: #<Class:Webmachine::Resource>#new
|
61
|
+
1: Webmachine::Resource::Callbacks#service_available?
|
62
|
+
2: Webmachine::Resource::Callbacks#known_methods
|
63
|
+
3: Webmachine::Resource::Callbacks#uri_too_long?
|
64
|
+
4: Resource#allowed_methods
|
65
|
+
…
|
66
|
+
31: Resource#finish_request
|
67
|
+
|
68
|
+
- Debugging
|
69
|
+
|
70
|
+
You can jump inside any method on the call stack through the 'enter-stack'
|
71
|
+
command. It accepts a breakpoint as an argument, but it is optional. The stack
|
72
|
+
can be navigated in 'real time' through the 'continue', 'next', and
|
73
|
+
'previous' commands.
|
74
|
+
|
75
|
+
[3] pry(main)> enter-stack
|
76
|
+
From: /Users/rob/.rbenv/… (shortened for README)
|
77
|
+
=> 36: def self.new(request, response)
|
78
|
+
37: instance = allocate
|
79
|
+
38: instance.instance_variable_set(:@request, request)
|
80
|
+
39: instance.instance_variable_set(:@response, response)
|
81
|
+
40: instance.send :initialize
|
82
|
+
41: instance
|
83
|
+
42: end
|
84
|
+
[4] pry(Resource)>
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
__PLATFORM SUPPORT__
|
89
|
+
|
90
|
+
- CRuby 1.9+
|
91
|
+
|
92
|
+
__INSTALL__
|
93
|
+
|
94
|
+
gem install irwebmachine
|
95
|
+
|
96
|
+
__LICENSE__
|
97
|
+
|
98
|
+
See LICENSE.txt
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems/package_task'
|
7
|
+
Gem::PackageTask.new Gem::Specification.load('irwebmachine.gemspec') do |pkg|
|
8
|
+
pkg.need_tar, pkg.need_zip = true, false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rake/testtask'
|
13
|
+
task :default => :test
|
14
|
+
Rake::TestTask.new do |t|
|
15
|
+
t.name = "test"
|
16
|
+
t.test_files = Dir["test/*_test.rb"]
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/irwebmachine/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Rob Gleeson"]
|
6
|
+
gem.email = ["rob@flowof.info"]
|
7
|
+
gem.description = %q{IRWebmachine is an interactive debugger you can use to
|
8
|
+
make, trace & debug HTTP requests destined for a
|
9
|
+
webmachine-ruby resource.}
|
10
|
+
gem.summary = gem.description
|
11
|
+
gem.homepage = "https://github.com/robgleeson/irwebmachine"
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
gem.name = "irwebmachine"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = IRWebmachine::VERSION
|
18
|
+
gem.add_runtime_dependency "uri-query_params", "~> 0.7.0"
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class IRWebmachine::Application
|
2
|
+
def initialize(app)
|
3
|
+
@app = to_app app
|
4
|
+
@req = nil
|
5
|
+
@res = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def unbox
|
9
|
+
@app
|
10
|
+
end
|
11
|
+
|
12
|
+
def last_response
|
13
|
+
@res || raise(RuntimeError, "No active request.", [])
|
14
|
+
end
|
15
|
+
|
16
|
+
def last_request
|
17
|
+
@req || raise(RuntimeError, "No active request.", [])
|
18
|
+
end
|
19
|
+
|
20
|
+
%w(get post delete put).each do |type|
|
21
|
+
define_method(type) do |*args|
|
22
|
+
@req = IRWebmachine::TracedRequest.new @app
|
23
|
+
@res = @req.trace(*[type, *args])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def to_app(obj)
|
29
|
+
is_module = obj.is_a? Module
|
30
|
+
is_resource = is_module && obj.ancestors.include?(Webmachine::Resource)
|
31
|
+
if is_resource
|
32
|
+
Webmachine::Application.new do |app|
|
33
|
+
app.routes do
|
34
|
+
add ["*"], obj
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
obj
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class IRWebmachine::Frame
|
2
|
+
def initialize(binding,event)
|
3
|
+
@binding = binding
|
4
|
+
@event = event
|
5
|
+
@file = binding.eval "__FILE__"
|
6
|
+
@lineno = binding.eval "__LINE__"
|
7
|
+
@method = binding.eval "__method__"
|
8
|
+
@klass = binding.eval("self").method(@method).owner
|
9
|
+
end
|
10
|
+
|
11
|
+
def ruby_call?
|
12
|
+
"call" == @event
|
13
|
+
end
|
14
|
+
|
15
|
+
def event?(type)
|
16
|
+
type.to_s == @event
|
17
|
+
end
|
18
|
+
|
19
|
+
def context
|
20
|
+
@binding
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"#{@klass}##{@method}"
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module IRWebmachine::IRB::Bundle
|
2
|
+
def app
|
3
|
+
IRWebmachine.app || raise(RuntimeError, "No app set. Use IRWebmachine.app= to set one.", [])
|
4
|
+
end
|
5
|
+
|
6
|
+
def show_stack(filter = //)
|
7
|
+
request = app.last_request
|
8
|
+
request.stack.each_with_index do |trace, int|
|
9
|
+
puts "#{int}> #{trace}" if trace.to_s =~ filter
|
10
|
+
end
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def enter_stack(num)
|
15
|
+
request = app.last_request
|
16
|
+
trace = request.stack[num]
|
17
|
+
puts "==> Entering #{trace.klass}##{trace.method}"
|
18
|
+
irb(trace.binding)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class IRWebmachine::Pry::EnterStack < Pry::ClassCommand
|
2
|
+
match 'enter-stack'
|
3
|
+
group 'irwebmachine'
|
4
|
+
description 'Enters the context of a method on the call stack for a webmachine request.'
|
5
|
+
banner <<-BANNER
|
6
|
+
enter-stack BREAKPOINT
|
7
|
+
|
8
|
+
Enters into the context of a method on the call stack for a webmachine
|
9
|
+
request. BREAKPOINT can be retrieved from the print-stack command.
|
10
|
+
BANNER
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@app = target.eval "app"
|
14
|
+
@pry = Pry.new :commands => IRWebmachine::Pry::Nav
|
15
|
+
@req = IRWebmachine::TracedRequest.new @app.unbox
|
16
|
+
end
|
17
|
+
|
18
|
+
def process
|
19
|
+
@req.trace! *@app.last_request
|
20
|
+
repl @req.stack.continue
|
21
|
+
end
|
22
|
+
private
|
23
|
+
def repl(frame)
|
24
|
+
until hit?
|
25
|
+
if breakpoint =~ frame.to_s
|
26
|
+
@hit = true
|
27
|
+
else
|
28
|
+
if @req.stack.exhausted?
|
29
|
+
raise Pry::CommandError, 'No matching breakpoint.'
|
30
|
+
else
|
31
|
+
frame = @req.stack.continue
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
case @pry.repl(frame.context)
|
36
|
+
when nil
|
37
|
+
# no-op (exit).
|
38
|
+
when :next
|
39
|
+
repl @req.stack.next
|
40
|
+
when :continue
|
41
|
+
repl @req.stack.continue
|
42
|
+
when :previous
|
43
|
+
repl @req.stack.previous
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def hit?
|
48
|
+
@hit
|
49
|
+
end
|
50
|
+
|
51
|
+
def breakpoint
|
52
|
+
@breakpoint ||= Regexp.new(args.first.to_s)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
set = Pry::CommandSet.new
|
57
|
+
set.commands["enter-stack"] = IRWebmachine::Pry::EnterStack
|
58
|
+
Pry.commands.import(set)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module IRWebmachine::Pry
|
2
|
+
Nav = Pry::CommandSet.new do
|
3
|
+
command("continue") { throw(:breakout, :continue) }
|
4
|
+
alias_command "c", "continue"
|
5
|
+
|
6
|
+
command("next") { throw(:breakout, :next) }
|
7
|
+
alias_command "n", "next"
|
8
|
+
|
9
|
+
command("prev") { throw(:breakout, :previous) }
|
10
|
+
alias_command "p", "prev"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
IRWebmachine::Pry::Nav.import(Pry.commands)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class IRWebmachine::Pry::PrintStack < Pry::ClassCommand
|
2
|
+
|
3
|
+
match 'print-stack'
|
4
|
+
description 'Prints the call stack for the previous webmachine request.'
|
5
|
+
group 'irwebmachine'
|
6
|
+
banner <<-BANNER
|
7
|
+
print-stack [OPTIONS]
|
8
|
+
|
9
|
+
Prints the stack of method calls(in order) made during the previous
|
10
|
+
webmachine request. The stack excludes calls that don't originate from
|
11
|
+
a subclass of Webmachine::Resource.
|
12
|
+
BANNER
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@app = target.eval "app"
|
16
|
+
end
|
17
|
+
|
18
|
+
def options(opt)
|
19
|
+
opt.on :f, 'Filter the stack with a regular expression.', optional: true
|
20
|
+
end
|
21
|
+
|
22
|
+
def process
|
23
|
+
frames = stack.to_a.map do |frame|
|
24
|
+
if frame.ruby_call? && frame.to_s =~ filter
|
25
|
+
frame.to_s
|
26
|
+
end
|
27
|
+
end.compact.join "\n"
|
28
|
+
stagger_output text.with_line_numbers(frames, 0)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def stack
|
33
|
+
@app.last_request.stack
|
34
|
+
end
|
35
|
+
|
36
|
+
def filter
|
37
|
+
Regexp.new(opts[:f].to_s)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
set = Pry::CommandSet.new
|
41
|
+
set.commands["print-stack"] = IRWebmachine::Pry::PrintStack
|
42
|
+
Pry.commands.import(set)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module IRWebmachine::Pry
|
2
|
+
require "pry"
|
3
|
+
require_relative "pry/print_stack"
|
4
|
+
require_relative "pry/enter_stack"
|
5
|
+
require_relative "pry/nav"
|
6
|
+
end
|
7
|
+
|
8
|
+
def app
|
9
|
+
IRWebmachine.app || raise(RuntimeError, "No app set. Use IRWebmachine.app= to set one.", [])
|
10
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class IRWebmachine::Stack
|
2
|
+
def initialize(stack = [])
|
3
|
+
@stack = stack
|
4
|
+
@index = -1
|
5
|
+
@tracer = IRWebmachine::Tracer.new
|
6
|
+
@tracer.events = ["call", "return"]
|
7
|
+
@tracer.targets = [Webmachine::Resource::Callbacks]
|
8
|
+
end
|
9
|
+
|
10
|
+
def push(*args)
|
11
|
+
@stack.push(*args)
|
12
|
+
end
|
13
|
+
alias_method :<<, :push
|
14
|
+
|
15
|
+
def tracer
|
16
|
+
@tracer
|
17
|
+
end
|
18
|
+
|
19
|
+
def previous
|
20
|
+
@index -= 1 if @index > 0
|
21
|
+
@stack[@index]
|
22
|
+
end
|
23
|
+
|
24
|
+
def exhausted?
|
25
|
+
@tracer.finished?
|
26
|
+
end
|
27
|
+
|
28
|
+
def continue
|
29
|
+
if exhausted?
|
30
|
+
if !at_end_of_stack
|
31
|
+
@index += 1
|
32
|
+
end
|
33
|
+
else
|
34
|
+
if at_end_of_stack
|
35
|
+
@stack << tracer.continue
|
36
|
+
end
|
37
|
+
@index += 1
|
38
|
+
end
|
39
|
+
@stack[@index]
|
40
|
+
end
|
41
|
+
|
42
|
+
def next
|
43
|
+
if at_end_of_stack
|
44
|
+
@stack[@index]
|
45
|
+
else
|
46
|
+
frame = nil
|
47
|
+
until frame && frame.ruby_call?
|
48
|
+
frame = continue
|
49
|
+
end
|
50
|
+
frame
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def at_end_of_stack
|
56
|
+
@index + 1 > @stack.size - 1
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class IRWebmachine::TracedRequest
|
2
|
+
#
|
3
|
+
# @param [Webmachine::Application] app
|
4
|
+
# An instance of Webmachine::Application.
|
5
|
+
#
|
6
|
+
# @return [IRWebmachine::TracedRequest]
|
7
|
+
# Returns an instance of IRWebmachine::TracedRequest.
|
8
|
+
#
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
@req = nil
|
12
|
+
@res = nil
|
13
|
+
@stack = IRWebmachine::Stack.new
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# @return [IRWebmachine::Stack]
|
18
|
+
# Returns an instance of Webmachine::Stack.
|
19
|
+
#
|
20
|
+
def stack
|
21
|
+
@stack
|
22
|
+
end
|
23
|
+
|
24
|
+
def trace(*args)
|
25
|
+
trace!(*args)
|
26
|
+
while frame = @stack.tracer.continue
|
27
|
+
@stack << frame
|
28
|
+
end
|
29
|
+
@res
|
30
|
+
end
|
31
|
+
|
32
|
+
def trace!(type, path, params = {}, headers = {}, body = "")
|
33
|
+
uri = URI::HTTP.build(host: "localhost", path: path)
|
34
|
+
uri.query_params.merge!(params)
|
35
|
+
@req = Webmachine::Request.new type.upcase, uri, headers, body
|
36
|
+
@res = Webmachine::Response.new
|
37
|
+
@stack.tracer.trace do
|
38
|
+
@app.dispatcher.dispatch @req, @res
|
39
|
+
end
|
40
|
+
@res
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# @return [Array]
|
45
|
+
# Returns the arguments given to {#trace} or {#trace!}.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
#
|
49
|
+
# @req.trace "GET", "/"
|
50
|
+
# @req.trace *@req # replay request.
|
51
|
+
#
|
52
|
+
def to_a
|
53
|
+
[@req.method, @req.uri.path, @req.query, @req.headers, @req.body]
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'thread'
|
2
|
+
class IRWebmachine::Tracer
|
3
|
+
def initialize
|
4
|
+
@thread = nil
|
5
|
+
@queue = SizedQueue.new(1)
|
6
|
+
@targets = [BasicObject]
|
7
|
+
@events = ["call", "c-call", "return", "c-return", "class", "end", "line",
|
8
|
+
"raise"]
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# The [set\_trace\_func](http://apidock.com/ruby/Kernel/set_trace_func) API
|
13
|
+
# documentation has a list of possible events.
|
14
|
+
#
|
15
|
+
# @param [Array<String>] events
|
16
|
+
# An array of events.
|
17
|
+
#
|
18
|
+
# @return [void]
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# #
|
22
|
+
# # Push a frame onto the queue when the "call" or "return" event is
|
23
|
+
# # emitted by set_trace_func. By default, all events push a frame
|
24
|
+
# # onto the queue.
|
25
|
+
# #
|
26
|
+
# tracer.events = ["call", "return"]
|
27
|
+
#
|
28
|
+
def events=(events)
|
29
|
+
@events = events
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# @param [Array<Module>] targets
|
34
|
+
# An array of classes/modules.
|
35
|
+
#
|
36
|
+
# @return [void]
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# #
|
40
|
+
# # Push a frame onto the queue when `binding.eval('self')` has
|
41
|
+
# # Webmachine::Resource::Callbacks somewhere in its ancestry tree.
|
42
|
+
# # By default, targets is equal to [BasicObject].
|
43
|
+
# #
|
44
|
+
# tracer.targets = [Webmachine::Resource::Callbacks]
|
45
|
+
#
|
46
|
+
def targets=(targets)
|
47
|
+
@targets = targets
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# @return [Boolean]
|
52
|
+
# Returns true when you cannot trace anymore(e.g: #continue returns nil).
|
53
|
+
#
|
54
|
+
def finished?
|
55
|
+
@queue.empty? && [false, nil].include?(@thread.status)
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
#
|
60
|
+
# @return [IRwebmachine::Frame]
|
61
|
+
# Returns an instance of {IRWebmachine::Frame}.
|
62
|
+
#
|
63
|
+
# @return [nil]
|
64
|
+
# Returns nil when the tracer has finished.
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# #
|
68
|
+
# # Each call to continue resumes & then suspends the tracer.
|
69
|
+
# # If you want to trace until there is no more code to trace you
|
70
|
+
# # could wrap this method in a loop.
|
71
|
+
# #
|
72
|
+
# tracer.continue
|
73
|
+
# tracer.continue
|
74
|
+
#
|
75
|
+
def continue
|
76
|
+
while @queue.empty?
|
77
|
+
sleep 0.01
|
78
|
+
return if finished?
|
79
|
+
end
|
80
|
+
@queue.deq
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# @overload def trace(&block)
|
85
|
+
#
|
86
|
+
# @param [Proc] block
|
87
|
+
# A block to execute inside the tracer.
|
88
|
+
#
|
89
|
+
# @return [void]
|
90
|
+
#
|
91
|
+
# @see Tracer#continue
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# tracer.trace do
|
95
|
+
# …
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
def trace
|
99
|
+
@thread ||=
|
100
|
+
Thread.new do
|
101
|
+
Thread.current.set_trace_func method(:tracer).to_proc
|
102
|
+
yield
|
103
|
+
Thread.current.set_trace_func(nil)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def tracer(event, file, lineno, id, binding, klass)
|
110
|
+
try do
|
111
|
+
has_ancestor = @targets.any? do |t|
|
112
|
+
# This says, "binding.eval('self') references a class or module,
|
113
|
+
# and the event(f. ex ':call') came from one of the iterated
|
114
|
+
# targets".
|
115
|
+
klass.is_a?(Module) && klass.ancestors.include?(t)
|
116
|
+
end
|
117
|
+
if has_ancestor && @events.include?(event)
|
118
|
+
frame = IRWebmachine::Frame.new(binding,event)
|
119
|
+
@queue.enq(frame)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def try
|
125
|
+
begin
|
126
|
+
yield
|
127
|
+
rescue SystemStackError => e
|
128
|
+
puts <<-CRASH
|
129
|
+
|
130
|
+
----------------------
|
131
|
+
CRASH (IRWebmachine)
|
132
|
+
----------------------
|
133
|
+
|
134
|
+
The tracer appears to be locked in infinite
|
135
|
+
recursion. This may be a bug in IRWebmachine,
|
136
|
+
but your own code may be the cause.
|
137
|
+
|
138
|
+
The tracer will stop execution now, but the
|
139
|
+
stack up to the point of infinite recursion
|
140
|
+
is retained for debugging.
|
141
|
+
|
142
|
+
CRASH
|
143
|
+
Thread.current.set_trace_func(nil)
|
144
|
+
rescue Exception => e
|
145
|
+
puts <<-CRASH
|
146
|
+
|
147
|
+
----------------------
|
148
|
+
CRASH (IRWebmachine)
|
149
|
+
----------------------
|
150
|
+
|
151
|
+
The tracer has crashed.
|
152
|
+
This is a bug in IRWebmachine.
|
153
|
+
|
154
|
+
EXCEPTION:
|
155
|
+
#{e.class}
|
156
|
+
|
157
|
+
MESSAGE:
|
158
|
+
#{e.message}
|
159
|
+
|
160
|
+
BACKTRACE:
|
161
|
+
#{e.backtrace.each { |line| puts(line) }}
|
162
|
+
CRASH
|
163
|
+
Thread.current.set_trace_func(nil)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
data/lib/irwebmachine.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module IRWebmachine
|
2
|
+
require "uri/query_params"
|
3
|
+
require_relative "irwebmachine/application"
|
4
|
+
require_relative "irwebmachine/traced_request"
|
5
|
+
require_relative "irwebmachine/frame"
|
6
|
+
require_relative "irwebmachine/tracer"
|
7
|
+
require_relative "irwebmachine/stack"
|
8
|
+
require_relative "irwebmachine/version"
|
9
|
+
|
10
|
+
def self.app=(app)
|
11
|
+
@app = Application.new app
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.app
|
15
|
+
@app
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Resource < Webmachine::Resource
|
2
|
+
def content_types_provided
|
3
|
+
[["plain/text", :to_text]]
|
4
|
+
end
|
5
|
+
|
6
|
+
def content_types_accepted
|
7
|
+
[["*/*", :accept]]
|
8
|
+
end
|
9
|
+
|
10
|
+
def allowed_methods
|
11
|
+
%w(GET POST DELETE PUT)
|
12
|
+
end
|
13
|
+
|
14
|
+
# POST
|
15
|
+
def process_post
|
16
|
+
response.body = "POST OK"
|
17
|
+
end
|
18
|
+
|
19
|
+
# GET
|
20
|
+
def to_text
|
21
|
+
response.body = "GET OK"
|
22
|
+
end
|
23
|
+
|
24
|
+
# PUT
|
25
|
+
def accept
|
26
|
+
response.body = "PUT OK"
|
27
|
+
end
|
28
|
+
|
29
|
+
# DELETE
|
30
|
+
def delete_resource
|
31
|
+
response.body = "DELETE OK"
|
32
|
+
end
|
33
|
+
|
34
|
+
def finish_request
|
35
|
+
response.headers['X-Request-Query'] = request.query
|
36
|
+
response.headers['X-Request-Headers'] = request.headers
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative "setup"
|
2
|
+
class IRWebmachine::ApplicationTest < Test::Unit::TestCase
|
3
|
+
def setup
|
4
|
+
IRWebmachine.app = app
|
5
|
+
@app = IRWebmachine.app
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_get
|
9
|
+
res = @app.get "/mock_application"
|
10
|
+
assert_equal "GET OK", res.body
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_post
|
14
|
+
res = @app.post "/mock_application"
|
15
|
+
assert_equal "POST OK", res.body
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_delete
|
19
|
+
res = @app.delete "/mock_application"
|
20
|
+
assert_equal "DELETE OK", res.body
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_put
|
24
|
+
res = @app.put "/mock_application"
|
25
|
+
assert_equal "PUT OK", res.body
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_query
|
29
|
+
%w(get post delete put).each do |verb|
|
30
|
+
res = @app.send(verb, "/mock_application", {"foo" => "bar"})
|
31
|
+
assert_equal({"foo" => "bar"}, res.headers['X-Request-Query'])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_headers
|
36
|
+
%w(get post delete put).each do |verb|
|
37
|
+
res = @app.send(verb, "/mock_application", {}, {'Answer' => '42'})
|
38
|
+
assert_equal({'Answer' => '42'}, res.headers['X-Request-Headers'])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def app
|
44
|
+
Webmachine::Application.new do |app|
|
45
|
+
app.routes do
|
46
|
+
add ["mock_application"], Resource
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/test/setup.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: irwebmachine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rob Gleeson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: uri-query_params
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.7.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.7.0
|
30
|
+
description: ! "IRWebmachine is an interactive debugger you can use to \n make,
|
31
|
+
trace & debug HTTP requests destined for a \n webmachine-ruby
|
32
|
+
resource."
|
33
|
+
email:
|
34
|
+
- rob@flowof.info
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- .gitignore
|
40
|
+
- .pryrc
|
41
|
+
- .travis.yml
|
42
|
+
- .yardopts
|
43
|
+
- Gemfile
|
44
|
+
- LICENSE.txt
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- irwebmachine.gemspec
|
48
|
+
- lib/irwebmachine.rb
|
49
|
+
- lib/irwebmachine/application.rb
|
50
|
+
- lib/irwebmachine/frame.rb
|
51
|
+
- lib/irwebmachine/irb.rb
|
52
|
+
- lib/irwebmachine/irb/bundle.rb
|
53
|
+
- lib/irwebmachine/pry.rb
|
54
|
+
- lib/irwebmachine/pry/enter_stack.rb
|
55
|
+
- lib/irwebmachine/pry/nav.rb
|
56
|
+
- lib/irwebmachine/pry/print_stack.rb
|
57
|
+
- lib/irwebmachine/stack.rb
|
58
|
+
- lib/irwebmachine/traced_request.rb
|
59
|
+
- lib/irwebmachine/tracer.rb
|
60
|
+
- lib/irwebmachine/version.rb
|
61
|
+
- test/fixtures/resource.rb
|
62
|
+
- test/irwebmachine_application_test.rb
|
63
|
+
- test/setup.rb
|
64
|
+
homepage: https://github.com/robgleeson/irwebmachine
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
hash: 541457732047797415
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
hash: 541457732047797415
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.8.23
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: IRWebmachine is an interactive debugger you can use to make, trace & debug
|
94
|
+
HTTP requests destined for a webmachine-ruby resource.
|
95
|
+
test_files:
|
96
|
+
- test/fixtures/resource.rb
|
97
|
+
- test/irwebmachine_application_test.rb
|
98
|
+
- test/setup.rb
|