actionpack 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- data/CHANGELOG +24 -0
- data/install.rb +9 -0
- data/lib/action_controller.rb +1 -0
- data/lib/action_controller/base.rb +7 -4
- data/lib/action_controller/{support → cgi_ext}/cookie_performance_fix.rb +0 -0
- data/lib/action_controller/cgi_process.rb +1 -1
- data/lib/action_controller/dependencies.rb +0 -19
- data/lib/action_controller/helpers.rb +1 -1
- data/lib/action_controller/layout.rb +16 -0
- data/lib/action_controller/response.rb +4 -2
- data/lib/action_controller/session/drb_server.rb +24 -1
- data/lib/action_controller/session/mem_cache_store.rb +95 -0
- data/lib/action_controller/support/binding_of_caller.rb +81 -0
- data/lib/action_controller/support/breakpoint.rb +525 -0
- data/lib/action_controller/support/dependencies.rb +71 -0
- data/lib/action_controller/support/misc.rb +29 -3
- data/lib/action_controller/support/module_attribute_accessors.rb +57 -0
- data/lib/action_controller/test_process.rb +20 -4
- data/lib/action_controller/url_rewriter.rb +4 -4
- data/lib/action_view/helpers/date_helper.rb +20 -0
- data/rakefile +5 -5
- data/test/controller/url_test.rb +31 -0
- data/test/template/date_helper_test.rb +54 -1
- data/test/template/url_helper_test.rb +1 -2
- metadata +15 -7
data/CHANGELOG
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
*1.2.0* (January 4th, 2005)
|
2
|
+
|
3
|
+
* Added MemCacheStore for storing session data in Danga's MemCache system [Bob Cottrell]
|
4
|
+
Depends on: MemCached server (http://www.danga.com/memcached/), MemCache client (http://raa.ruby-lang.org/project/memcache/)
|
5
|
+
|
6
|
+
* Added thread-safety to the DRbStore #66, #389 [Ben Stiglitz]
|
7
|
+
|
8
|
+
* Added DateHelper#select_time and DateHelper#select_second #373 [Scott Baron]
|
9
|
+
|
10
|
+
* Added :host and :protocol options to url_for and friends to redirect to another host and protocol than the current.
|
11
|
+
|
12
|
+
* Added class declaration for the MissingFile exception #388 [Kent Sibilev]
|
13
|
+
|
14
|
+
* Added "short hypertext note with a hyperlink to the new URI(s)" to redirects to fulfill compliance with RFC 2616 (HTTP/1.1) section 10.3.3 #397 [Tim Bates]
|
15
|
+
|
16
|
+
* Added second boolean parameter to Base.redirect_to_url and Response#redirect to control whether the redirect is permanent or not (301 vs 302) #375 [Hodel]
|
17
|
+
|
18
|
+
* Fixed redirects when the controller and action is named the same. Still haven't fixed same controller, module, and action, though #201 [Josh]
|
19
|
+
|
20
|
+
* Fixed problems with running multiple functional tests in Rails under 1.8.2 by including hack for test/unit weirdness
|
21
|
+
|
22
|
+
* Fixed that @request.remote_ip didn't work in the test environment #369 [Bruno Mattarollo]
|
23
|
+
|
24
|
+
|
1
25
|
*1.1.0*
|
2
26
|
|
3
27
|
* Added search through session to clear out association caches at the end of each request. This makes it possible to place Active Record objects
|
data/install.rb
CHANGED
@@ -40,8 +40,10 @@ files = %w-
|
|
40
40
|
action_controller/benchmarking.rb
|
41
41
|
action_controller/cgi_ext/cgi_ext.rb
|
42
42
|
action_controller/cgi_ext/cgi_methods.rb
|
43
|
+
action_controller/cgi_ext/cookie_performance_fix.rb
|
43
44
|
action_controller/cgi_process.rb
|
44
45
|
action_controller/cookies.rb
|
46
|
+
action_controller/dependencies.rb
|
45
47
|
action_controller/filters.rb
|
46
48
|
action_controller/flash.rb
|
47
49
|
action_controller/helpers.rb
|
@@ -53,11 +55,18 @@ files = %w-
|
|
53
55
|
action_controller/session/active_record_store.rb
|
54
56
|
action_controller/session/drb_server.rb
|
55
57
|
action_controller/session/drb_store.rb
|
58
|
+
action_controller/session/mem_cache_store.rb
|
59
|
+
action_controller/session.rb
|
56
60
|
action_controller/support/class_inheritable_attributes.rb
|
57
61
|
action_controller/support/class_attribute_accessors.rb
|
58
62
|
action_controller/support/clean_logger.rb
|
59
63
|
action_controller/support/cookie_performance_fix.rb
|
60
64
|
action_controller/support/inflector.rb
|
65
|
+
action_controller/support/binding_of_caller.rb
|
66
|
+
action_controller/support/breakpoint.rb
|
67
|
+
action_controller/support/dependencies.rb
|
68
|
+
action_controller/support/misc.rb
|
69
|
+
action_controller/support/module_attribute_accessors.rb
|
61
70
|
action_controller/templates/rescues/_request_and_response.rhtml
|
62
71
|
action_controller/templates/rescues/diagnostics.rhtml
|
63
72
|
action_controller/templates/rescues/layout.rhtml
|
data/lib/action_controller.rb
CHANGED
@@ -25,6 +25,7 @@ $:.unshift(File.dirname(__FILE__))
|
|
25
25
|
|
26
26
|
require 'action_controller/support/clean_logger'
|
27
27
|
require 'action_controller/support/misc'
|
28
|
+
require 'action_controller/support/dependencies'
|
28
29
|
|
29
30
|
require 'action_controller/base'
|
30
31
|
require 'action_controller/rescue'
|
@@ -14,6 +14,8 @@ module ActionController #:nodoc:
|
|
14
14
|
end
|
15
15
|
class UnknownAction < ActionControllerError #:nodoc:
|
16
16
|
end
|
17
|
+
class MissingFile < ActionControllerError #:nodoc:
|
18
|
+
end
|
17
19
|
|
18
20
|
# Action Controllers are made up of one or more actions that performs its purpose and then either renders a template or
|
19
21
|
# redirects to another action. An action is defined as a public method on the controller, which will automatically be
|
@@ -432,7 +434,7 @@ module ActionController #:nodoc:
|
|
432
434
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
|
433
435
|
# for the Cache-Control header spec.
|
434
436
|
def send_file(path, options = {}) #:doc:
|
435
|
-
raise MissingFile unless File.file?(path) and File.readable?(path)
|
437
|
+
raise MissingFile, path unless File.file?(path) and File.readable?(path)
|
436
438
|
|
437
439
|
options[:length] ||= File.size(path)
|
438
440
|
options[:filename] ||= File.basename(path)
|
@@ -529,10 +531,11 @@ module ActionController #:nodoc:
|
|
529
531
|
end
|
530
532
|
|
531
533
|
# Redirects the browser to the specified <tt>url</tt>. Used to redirect outside of the current application. Example:
|
532
|
-
# <tt>redirect_to_url "http://www.rubyonrails.org"</tt>.
|
533
|
-
|
534
|
+
# <tt>redirect_to_url "http://www.rubyonrails.org"</tt>. If the resource has moved permanently, it's possible to pass true as the
|
535
|
+
# second parameter and the browser will get "301 Moved Permanently" instead of "302 Found".
|
536
|
+
def redirect_to_url(url, permanently = false) #:doc:
|
534
537
|
logger.info("Redirected to #{url}") unless logger.nil?
|
535
|
-
@response.redirect(url)
|
538
|
+
@response.redirect(url, permanently)
|
536
539
|
@performed_redirect = true
|
537
540
|
end
|
538
541
|
|
File without changes
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'action_controller/cgi_ext/cgi_ext'
|
2
|
-
require 'action_controller/
|
2
|
+
require 'action_controller/cgi_ext/cookie_performance_fix'
|
3
3
|
require 'action_controller/session/drb_store'
|
4
4
|
require 'action_controller/session/active_record_store'
|
5
5
|
|
@@ -1,22 +1,8 @@
|
|
1
|
-
unless Object.respond_to?(:require_dependency)
|
2
|
-
Object.send(:define_method, :require_dependency) { |file_name| ActionController::Base.require_dependency(file_name) }
|
3
|
-
end
|
4
|
-
|
5
1
|
module ActionController #:nodoc:
|
6
2
|
module Dependencies #:nodoc:
|
7
3
|
def self.append_features(base)
|
8
4
|
super
|
9
|
-
|
10
|
-
base.class_eval do
|
11
|
-
# When turned on (which is default), all dependencies are included using "load". This mean that any change is instant in cached
|
12
|
-
# environments like mod_ruby or FastCGI. When set to false, "require" is used, which is faster but requires server restart to
|
13
|
-
# be effective.
|
14
|
-
@@reload_dependencies = true
|
15
|
-
cattr_accessor :reload_dependencies
|
16
|
-
end
|
17
|
-
|
18
5
|
base.class_eval { class << self; alias_method :inherited_without_model, :inherited; end }
|
19
|
-
|
20
6
|
base.extend(ClassMethods)
|
21
7
|
end
|
22
8
|
|
@@ -41,11 +27,6 @@ module ActionController #:nodoc:
|
|
41
27
|
# # helper :post (already required)
|
42
28
|
# end
|
43
29
|
module ClassMethods
|
44
|
-
# Loads the <tt>file_name</tt> if reload_dependencies is true or requires if it's false.
|
45
|
-
def require_dependency(file_name)
|
46
|
-
reload_dependencies ? silence_warnings { load("#{file_name}.rb") } : require(file_name)
|
47
|
-
end
|
48
|
-
|
49
30
|
# Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar
|
50
31
|
# backend for modelling entity classes.
|
51
32
|
def model(*models)
|
@@ -115,6 +115,22 @@ module ActionController #:nodoc:
|
|
115
115
|
# a given action without a layout. Just use the method <tt>render_without_layout</tt>, which works just like Base.render --
|
116
116
|
# it just doesn't apply any layouts.
|
117
117
|
#
|
118
|
+
# == Using a different layout in the action render call
|
119
|
+
#
|
120
|
+
# If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
|
121
|
+
# Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
|
122
|
+
# This is possible using <tt>render_with_layout</tt> method. It's just a bit more manual work as you'll have to supply fully
|
123
|
+
# qualified template and layout names as this example shows:
|
124
|
+
#
|
125
|
+
# class WeblogController < ActionController::Base
|
126
|
+
# def help
|
127
|
+
# render_with_layout "help/index", "200", "layouts/help"
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
|
132
|
+
# as the third.
|
133
|
+
#
|
118
134
|
# == Automatic layout assignment
|
119
135
|
#
|
120
136
|
# If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
|
@@ -7,9 +7,11 @@ module ActionController
|
|
7
7
|
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
|
8
8
|
end
|
9
9
|
|
10
|
-
def redirect(to_url)
|
11
|
-
@headers["Status"] = "
|
10
|
+
def redirect(to_url, permanently = false)
|
11
|
+
@headers["Status"] = permanently ? "301 Moved Permanently" : "302 Found"
|
12
12
|
@headers["location"] = to_url
|
13
|
+
|
14
|
+
@body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -5,5 +5,28 @@
|
|
5
5
|
|
6
6
|
require 'drb'
|
7
7
|
|
8
|
-
|
8
|
+
session_hash = Hash.new
|
9
|
+
session_hash.instance_eval { @mutex = Mutex.new }
|
10
|
+
|
11
|
+
class <<session_hash
|
12
|
+
def []=(key, value)
|
13
|
+
@mutex.synchronize do
|
14
|
+
super(key, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
@mutex.synchronize do
|
20
|
+
super(key)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
@mutex.synchronize do
|
26
|
+
super(key)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
DRb.start_service('druby://127.0.0.1:9192', session_hash)
|
9
32
|
DRb.thread.join
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# cgi/session/memcached.rb - persistent storage of marshalled session data
|
2
|
+
#
|
3
|
+
# == Overview
|
4
|
+
#
|
5
|
+
# This file provides the CGI::Session::MemCache class, which builds
|
6
|
+
# persistence of storage data on top of the MemCache library. See
|
7
|
+
# cgi/session.rb for more details on session storage managers.
|
8
|
+
#
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'cgi/session'
|
12
|
+
require 'memcache'
|
13
|
+
|
14
|
+
class CGI
|
15
|
+
class Session
|
16
|
+
# MemCache-based session storage class.
|
17
|
+
#
|
18
|
+
# This builds upon the top-level MemCache class provided by the
|
19
|
+
# library file memcache.rb. Session data is marshalled and stored
|
20
|
+
# in a memcached cache.
|
21
|
+
class MemCacheStore
|
22
|
+
def check_id(id) #:nodoc:#
|
23
|
+
/[^0-9a-zA-Z]+/ =~ id.to_s ? false : true
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create a new CGI::Session::MemCache instance
|
27
|
+
#
|
28
|
+
# This constructor is used internally by CGI::Session. The
|
29
|
+
# user does not generally need to call it directly.
|
30
|
+
#
|
31
|
+
# +session+ is the session for which this instance is being
|
32
|
+
# created. The session id must only contain alphanumeric
|
33
|
+
# characters; automatically generated session ids observe
|
34
|
+
# this requirement.
|
35
|
+
#
|
36
|
+
# +option+ is a hash of options for the initialiser. The
|
37
|
+
# following options are recognized:
|
38
|
+
#
|
39
|
+
# cache:: an instance of a MemCache client to use as the
|
40
|
+
# session cache.
|
41
|
+
#
|
42
|
+
# This session's memcache entry will be created if it does
|
43
|
+
# not exist, or retrieved if it does.
|
44
|
+
def initialize(session, options = {})
|
45
|
+
id = session.session_id
|
46
|
+
unless check_id(id)
|
47
|
+
raise ArgumentError, "session_id '%s' is invalid" % id
|
48
|
+
end
|
49
|
+
@cache = options['cache']
|
50
|
+
@session_key = "session:#{id}"
|
51
|
+
@hash = {}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Restore session state from the session's memcache entry.
|
55
|
+
#
|
56
|
+
# Returns the session state as a hash.
|
57
|
+
def restore
|
58
|
+
begin
|
59
|
+
@hash = @cache[@session_key]
|
60
|
+
rescue
|
61
|
+
# Ignore session get failures.
|
62
|
+
end
|
63
|
+
@hash = {} unless @hash
|
64
|
+
@hash
|
65
|
+
end
|
66
|
+
|
67
|
+
# Save session state to the session's memcache entry.
|
68
|
+
def update
|
69
|
+
begin
|
70
|
+
@cache[@session_key] = @hash
|
71
|
+
rescue
|
72
|
+
# Ignore session update failures.
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Update and close the session's memcache entry.
|
77
|
+
def close
|
78
|
+
update
|
79
|
+
end
|
80
|
+
|
81
|
+
# Delete the session's memcache entry.
|
82
|
+
def delete
|
83
|
+
begin
|
84
|
+
@cache.delete(@session_key)
|
85
|
+
rescue
|
86
|
+
# Ignore session delete failures.
|
87
|
+
end
|
88
|
+
@hash = {}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
rescue LoadError
|
94
|
+
# MemCache wasn't available so neither can the store be
|
95
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
begin
|
2
|
+
require 'simplecc'
|
3
|
+
rescue LoadError
|
4
|
+
def Continuation.create(*args, &block)
|
5
|
+
cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
|
6
|
+
result ||= args
|
7
|
+
return *[cc, *result]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# This method returns the binding of the method that called your
|
12
|
+
# method. It will raise an Exception when you're not inside a method.
|
13
|
+
#
|
14
|
+
# It's used like this:
|
15
|
+
# def inc_counter(amount = 1)
|
16
|
+
# Binding.of_caller do |binding|
|
17
|
+
# # Create a lambda that will increase the variable 'counter'
|
18
|
+
# # in the caller of this method when called.
|
19
|
+
# inc = eval("lambda { |arg| counter += arg }", binding)
|
20
|
+
# # We can refer to amount from inside this block safely.
|
21
|
+
# inc.call(amount)
|
22
|
+
# end
|
23
|
+
# # No other statements can go here. Put them inside the block.
|
24
|
+
# end
|
25
|
+
# counter = 0
|
26
|
+
# 2.times { inc_counter }
|
27
|
+
# counter # => 2
|
28
|
+
#
|
29
|
+
# Binding.of_caller must be the last statement in the method.
|
30
|
+
# This means that you will have to put everything you want to
|
31
|
+
# do after the call to Binding.of_caller into the block of it.
|
32
|
+
# This should be no problem however, because Ruby has closures.
|
33
|
+
# If you don't do this an Exception will be raised. Because of
|
34
|
+
# the way that Binding.of_caller is implemented it has to be
|
35
|
+
# done this way.
|
36
|
+
def Binding.of_caller(&block)
|
37
|
+
old_critical = Thread.critical
|
38
|
+
Thread.critical = true
|
39
|
+
count = 0
|
40
|
+
cc, result, error, extra_data = Continuation.create(nil, nil)
|
41
|
+
error.call if error
|
42
|
+
|
43
|
+
tracer = lambda do |*args|
|
44
|
+
type, context, extra_data = args[0], args[4], args
|
45
|
+
if type == "return"
|
46
|
+
count += 1
|
47
|
+
# First this method and then calling one will return --
|
48
|
+
# the trace event of the second event gets the context
|
49
|
+
# of the method which called the method that called this
|
50
|
+
# method.
|
51
|
+
if count == 2
|
52
|
+
# It would be nice if we could restore the trace_func
|
53
|
+
# that was set before we swapped in our own one, but
|
54
|
+
# this is impossible without overloading set_trace_func
|
55
|
+
# in current Ruby.
|
56
|
+
set_trace_func(nil)
|
57
|
+
cc.call(eval("binding", context), nil, extra_data)
|
58
|
+
end
|
59
|
+
elsif type == "line" then
|
60
|
+
nil
|
61
|
+
elsif type == "c-return" and extra_data[3] == :set_trace_func then
|
62
|
+
nil
|
63
|
+
else
|
64
|
+
set_trace_func(nil)
|
65
|
+
error_msg = "Binding.of_caller used in non-method context or " +
|
66
|
+
"trailing statements of method using it aren't in the block."
|
67
|
+
cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
unless result
|
72
|
+
set_trace_func(tracer)
|
73
|
+
return nil
|
74
|
+
else
|
75
|
+
Thread.critical = old_critical
|
76
|
+
case block.arity
|
77
|
+
when 1 then yield(result)
|
78
|
+
else yield(result, extra_data)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,525 @@
|
|
1
|
+
# The Breakpoint library provides the convenience of
|
2
|
+
# being able to inspect and modify state, diagnose
|
3
|
+
# bugs all via IRB by simply setting breakpoints in
|
4
|
+
# your applications by the call of a method.
|
5
|
+
#
|
6
|
+
# This library was written and is supported by me,
|
7
|
+
# Florian Gross. I can be reached at flgr@ccan.de
|
8
|
+
# and enjoy getting feedback about my libraries.
|
9
|
+
#
|
10
|
+
# The whole library (including breakpoint_client.rb
|
11
|
+
# and binding_of_caller.rb) is licensed under the
|
12
|
+
# same license that Ruby uses. (Which is currently
|
13
|
+
# either the GNU General Public License or a custom
|
14
|
+
# one that allows for commercial usage.) If you for
|
15
|
+
# some good reason need to use this under another
|
16
|
+
# license please contact me.
|
17
|
+
|
18
|
+
require 'irb'
|
19
|
+
# require 'binding_of_caller' <- Needs this
|
20
|
+
require 'drb'
|
21
|
+
require 'drb/acl'
|
22
|
+
|
23
|
+
module Breakpoint
|
24
|
+
extend self
|
25
|
+
|
26
|
+
# This will pop up an interactive ruby session at a
|
27
|
+
# pre-defined break point in a Ruby application. In
|
28
|
+
# this session you can examine the environment of
|
29
|
+
# the break point.
|
30
|
+
#
|
31
|
+
# You can get a list of variables in the context using
|
32
|
+
# local_variables via +local_variables+. You can then
|
33
|
+
# examine their values by typing their names.
|
34
|
+
#
|
35
|
+
# You can have a look at the call stack via +caller+.
|
36
|
+
#
|
37
|
+
# The source code around the location where the breakpoint
|
38
|
+
# was executed can be examined via +source_lines+. Its
|
39
|
+
# argument specifies how much lines of context to display.
|
40
|
+
# The default amount of context is 5 lines. Note that
|
41
|
+
# the call to +source_lines+ can raise an exception when
|
42
|
+
# it isn't able to read in the source code.
|
43
|
+
#
|
44
|
+
# breakpoints can also return a value. They will execute
|
45
|
+
# a supplied block for getting a default return value.
|
46
|
+
# A custom value can be returned from the session by doing
|
47
|
+
# +throw(:debug_return, value)+.
|
48
|
+
#
|
49
|
+
# You can also give names to break points which will be
|
50
|
+
# used in the message that is displayed upon execution
|
51
|
+
# of them.
|
52
|
+
#
|
53
|
+
# Here's a sample of how breakpoints should be placed:
|
54
|
+
#
|
55
|
+
# class Person
|
56
|
+
# def initialize(name, age)
|
57
|
+
# @name, @age = name, age
|
58
|
+
# breakpoint("Person#initialize")
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# attr_reader :age
|
62
|
+
# def name
|
63
|
+
# breakpoint("Person#name") { @name }
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# person = Person.new("Random Person", 23)
|
68
|
+
# puts "Name: #{person.name}"
|
69
|
+
#
|
70
|
+
# And here is a sample debug session:
|
71
|
+
#
|
72
|
+
# Executing break point "Person#initialize" at file.rb:4 in `initialize'
|
73
|
+
# irb(#<Person:0x292fbe8>):001:0> local_variables
|
74
|
+
# => ["name", "age", "_", "__"]
|
75
|
+
# irb(#<Person:0x292fbe8>):002:0> [name, age]
|
76
|
+
# => ["Random Person", 23]
|
77
|
+
# irb(#<Person:0x292fbe8>):003:0> [@name, @age]
|
78
|
+
# => ["Random Person", 23]
|
79
|
+
# irb(#<Person:0x292fbe8>):004:0> self
|
80
|
+
# => #<Person:0x292fbe8 @age=23, @name="Random Person">
|
81
|
+
# irb(#<Person:0x292fbe8>):005:0> @age += 1; self
|
82
|
+
# => #<Person:0x292fbe8 @age=24, @name="Random Person">
|
83
|
+
# irb(#<Person:0x292fbe8>):006:0> exit
|
84
|
+
# Executing break point "Person#name" at file.rb:9 in `name'
|
85
|
+
# irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
|
86
|
+
# Name: Overriden name
|
87
|
+
#
|
88
|
+
# Breakpoint sessions will automatically have a few
|
89
|
+
# convenience methods available. See Breakpoint::CommandBundle
|
90
|
+
# for a list of them.
|
91
|
+
#
|
92
|
+
# Breakpoints can also be used remotely over sockets.
|
93
|
+
# This is implemented by running part of the IRB session
|
94
|
+
# in the application and part of it in a special client.
|
95
|
+
# You have to call Breakpoint.activate_drb to enable
|
96
|
+
# support for remote breakpoints and then run
|
97
|
+
# breakpoint_client.rb which is distributed with this
|
98
|
+
# library. See the documentation of Breakpoint.activate_drb
|
99
|
+
# for details.
|
100
|
+
def breakpoint(id = nil, context = nil, &block)
|
101
|
+
callstack = caller
|
102
|
+
callstack.slice!(0, 3) if callstack.first["breakpoint"]
|
103
|
+
file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
|
104
|
+
|
105
|
+
message = "Executing break point " + (id ? "#{id.inspect} " : "") +
|
106
|
+
"at #{file}:#{line}" + (method ? " in `#{method}'" : "")
|
107
|
+
|
108
|
+
if context then
|
109
|
+
return handle_breakpoint(context, message, file, line, &block)
|
110
|
+
end
|
111
|
+
|
112
|
+
Binding.of_caller do |binding_context|
|
113
|
+
handle_breakpoint(binding_context, message, file, line, &block)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
module CommandBundle #:nodoc:
|
118
|
+
# Proxy to a Breakpoint client. Lets you directly execute code
|
119
|
+
# in the context of the client.
|
120
|
+
class Client#:nodoc:
|
121
|
+
def initialize(eval_handler) # :nodoc:
|
122
|
+
@eval_handler = eval_handler
|
123
|
+
end
|
124
|
+
|
125
|
+
instance_methods.each do |method|
|
126
|
+
next if method[/^__.+__$/]
|
127
|
+
undef_method method
|
128
|
+
end
|
129
|
+
|
130
|
+
# Executes the specified code at the client.
|
131
|
+
def eval(code)
|
132
|
+
@eval_handler.call(code)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Will execute the specified statement at the client.
|
136
|
+
def method_missing(method, *args)
|
137
|
+
if args.empty?
|
138
|
+
result = eval("#{method}")
|
139
|
+
else
|
140
|
+
result = eval("#{method}(*Marshal.load(#{Marshal.dump(args).inspect}))")
|
141
|
+
end
|
142
|
+
|
143
|
+
unless [true, false, nil].include?(result)
|
144
|
+
result.extend(DRbUndumped) if result
|
145
|
+
end
|
146
|
+
|
147
|
+
return result
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns the source code surrounding the location where the
|
152
|
+
# breakpoint was issued.
|
153
|
+
def source_lines(context = 5, return_line_numbers = false)
|
154
|
+
lines = File.readlines(@__bp_file).map { |line| line.chomp }
|
155
|
+
|
156
|
+
break_line = @__bp_line
|
157
|
+
start_line = [break_line - context, 1].max
|
158
|
+
end_line = break_line + context
|
159
|
+
|
160
|
+
result = lines[(start_line - 1) .. (end_line - 1)]
|
161
|
+
|
162
|
+
if return_line_numbers then
|
163
|
+
return [start_line, break_line, result]
|
164
|
+
else
|
165
|
+
return result
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Lets an object that will forward method calls to the breakpoint
|
170
|
+
# client. This is useful for outputting longer things at the client
|
171
|
+
# and so on. You can for example do these things:
|
172
|
+
#
|
173
|
+
# client.puts "Hello" # outputs "Hello" at client console
|
174
|
+
# # outputs "Hello" into the file temp.txt at the client
|
175
|
+
# client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
|
176
|
+
def client()
|
177
|
+
if Breakpoint.use_drb? then
|
178
|
+
Client.new(Breakpoint.drb_service.eval_handler)
|
179
|
+
else
|
180
|
+
Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def handle_breakpoint(context, message, file = "", line = "", &block) # :nodoc:
|
186
|
+
catch(:debug_return) do |value|
|
187
|
+
eval(%{
|
188
|
+
@__bp_file = #{file.inspect}
|
189
|
+
@__bp_line = #{line}
|
190
|
+
extend Breakpoint::CommandBundle
|
191
|
+
extend DRbUndumped if self
|
192
|
+
}, context) rescue nil
|
193
|
+
|
194
|
+
if not use_drb? then
|
195
|
+
puts message
|
196
|
+
IRB.start(nil, IRB::WorkSpace.new(context))
|
197
|
+
else
|
198
|
+
@drb_service.add_breakpoint(context, message)
|
199
|
+
end
|
200
|
+
|
201
|
+
block.call if block
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# These exceptions will be raised on failed asserts
|
206
|
+
# if Breakpoint.asserts_cause_exceptions is set to
|
207
|
+
# true.
|
208
|
+
class FailedAssertError < RuntimeError#:nodoc:
|
209
|
+
end
|
210
|
+
|
211
|
+
# This asserts that the block evaluates to true.
|
212
|
+
# If it doesn't evaluate to true a breakpoint will
|
213
|
+
# automatically be created at that execution point.
|
214
|
+
#
|
215
|
+
# You can disable assert checking in production
|
216
|
+
# code by setting Breakpoint.optimize_asserts to
|
217
|
+
# true. (It will still be enabled when Ruby is run
|
218
|
+
# via the -d argument.)
|
219
|
+
#
|
220
|
+
# Example:
|
221
|
+
# person_name = "Foobar"
|
222
|
+
# assert { not person_name.nil? }
|
223
|
+
#
|
224
|
+
# Note: If you want to use this method from an
|
225
|
+
# unit test, you will have to call it by its full
|
226
|
+
# name, Breakpoint.assert.
|
227
|
+
def assert(context = nil, &condition)
|
228
|
+
return if Breakpoint.optimize_asserts and not $DEBUG
|
229
|
+
return if yield
|
230
|
+
|
231
|
+
callstack = caller
|
232
|
+
callstack.slice!(0, 3) if callstack.first["assert"]
|
233
|
+
file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
|
234
|
+
|
235
|
+
message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."
|
236
|
+
|
237
|
+
if Breakpoint.asserts_cause_exceptions and not $DEBUG then
|
238
|
+
raise(Breakpoint::FailedAssertError, message)
|
239
|
+
end
|
240
|
+
|
241
|
+
message += " Executing implicit breakpoint."
|
242
|
+
|
243
|
+
if context then
|
244
|
+
return handle_breakpoint(context, message, file, line)
|
245
|
+
end
|
246
|
+
|
247
|
+
Binding.of_caller do |context|
|
248
|
+
handle_breakpoint(context, message, file, line)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Whether asserts should be ignored if not in debug mode.
|
253
|
+
# Debug mode can be enabled by running ruby with the -d
|
254
|
+
# switch or by setting $DEBUG to true.
|
255
|
+
attr_accessor :optimize_asserts
|
256
|
+
self.optimize_asserts = false
|
257
|
+
|
258
|
+
# Whether an Exception should be raised on failed asserts
|
259
|
+
# in non-$DEBUG code or not. By default this is disabled.
|
260
|
+
attr_accessor :asserts_cause_exceptions
|
261
|
+
self.asserts_cause_exceptions = false
|
262
|
+
@use_drb = false
|
263
|
+
|
264
|
+
attr_reader :drb_service # :nodoc:
|
265
|
+
|
266
|
+
class DRbService # :nodoc:
|
267
|
+
include DRbUndumped
|
268
|
+
|
269
|
+
def initialize
|
270
|
+
@handler = @eval_handler = @collision_handler = nil
|
271
|
+
|
272
|
+
IRB.instance_eval { @CONF[:RC] = true }
|
273
|
+
IRB.run_config
|
274
|
+
end
|
275
|
+
|
276
|
+
def collision
|
277
|
+
sleep(0.5) until @collision_handler
|
278
|
+
|
279
|
+
@collision_handler.call
|
280
|
+
end
|
281
|
+
|
282
|
+
def ping; end
|
283
|
+
|
284
|
+
def add_breakpoint(context, message)
|
285
|
+
workspace = IRB::WorkSpace.new(context)
|
286
|
+
workspace.extend(DRbUndumped)
|
287
|
+
|
288
|
+
sleep(0.5) until @handler
|
289
|
+
|
290
|
+
@handler.call(workspace, message)
|
291
|
+
end
|
292
|
+
|
293
|
+
def register_handler(&block)
|
294
|
+
@handler = block
|
295
|
+
end
|
296
|
+
|
297
|
+
def unregister_handler
|
298
|
+
@handler = nil
|
299
|
+
end
|
300
|
+
|
301
|
+
attr_reader :eval_handler
|
302
|
+
|
303
|
+
def register_eval_handler(&block)
|
304
|
+
@eval_handler = block
|
305
|
+
end
|
306
|
+
|
307
|
+
def unregister_eval_handler
|
308
|
+
@eval_handler = lambda { }
|
309
|
+
end
|
310
|
+
|
311
|
+
def register_collision_handler(&block)
|
312
|
+
@collision_handler = block
|
313
|
+
end
|
314
|
+
|
315
|
+
def unregister_collision_handler
|
316
|
+
@collision_handler = lambda { }
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Will run Breakpoint in DRb mode. This will spawn a server
|
321
|
+
# that can be attached to via the breakpoint-client command
|
322
|
+
# whenever a breakpoint is executed. This is useful when you
|
323
|
+
# are debugging CGI applications or other applications where
|
324
|
+
# you can't access debug sessions via the standard input and
|
325
|
+
# output of your application.
|
326
|
+
#
|
327
|
+
# You can specify an URI where the DRb server will run at.
|
328
|
+
# This way you can specify the port the server runs on. The
|
329
|
+
# default URI is druby://localhost:42531.
|
330
|
+
#
|
331
|
+
# Please note that breakpoints will be skipped silently in
|
332
|
+
# case the DRb server can not spawned. (This can happen if
|
333
|
+
# the port is already used by another instance of your
|
334
|
+
# application on CGI or another application.)
|
335
|
+
#
|
336
|
+
# Also note that by default this will only allow access
|
337
|
+
# from localhost. You can however specify a list of
|
338
|
+
# allowed hosts or nil (to allow access from everywhere).
|
339
|
+
# But that will still not protect you from somebody
|
340
|
+
# reading the data as it goes through the net.
|
341
|
+
#
|
342
|
+
# A good approach for getting security and remote access
|
343
|
+
# is setting up an SSH tunnel between the DRb service
|
344
|
+
# and the client. This is usually done like this:
|
345
|
+
#
|
346
|
+
# $ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com
|
347
|
+
# (This will connect port 20000 at the client side to port
|
348
|
+
# 20000 at the server side, and port 10000 at the server
|
349
|
+
# side to port 10000 at the client side.)
|
350
|
+
#
|
351
|
+
# After that do this on the server side: (the code being debugged)
|
352
|
+
# Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")
|
353
|
+
#
|
354
|
+
# And at the client side:
|
355
|
+
# ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
|
356
|
+
#
|
357
|
+
# Running through such a SSH proxy will also let you use
|
358
|
+
# breakpoint.rb in case you are behind a firewall.
|
359
|
+
#
|
360
|
+
# Detailed information about running DRb through firewalls is
|
361
|
+
# available at http://www.rubygarden.org/ruby?DrbTutorial
|
362
|
+
def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'], ignore_collisions = false) #:nodoc:
|
363
|
+
|
364
|
+
return false if @use_drb
|
365
|
+
|
366
|
+
uri ||= 'druby://localhost:42531'
|
367
|
+
|
368
|
+
if allowed_hosts then
|
369
|
+
acl = ["deny", "all"]
|
370
|
+
|
371
|
+
Array(allowed_hosts).each do |host|
|
372
|
+
acl += ["allow", host]
|
373
|
+
end
|
374
|
+
|
375
|
+
DRb.install_acl(ACL.new(acl))
|
376
|
+
end
|
377
|
+
|
378
|
+
@use_drb = true
|
379
|
+
@drb_service = DRbService.new
|
380
|
+
did_collision = false
|
381
|
+
begin
|
382
|
+
@service = DRb.start_service(uri, @drb_service)
|
383
|
+
rescue Errno::EADDRINUSE
|
384
|
+
if ignore_collisions then
|
385
|
+
nil
|
386
|
+
else
|
387
|
+
# The port is already occupied by another
|
388
|
+
# Breakpoint service. We will try to tell
|
389
|
+
# the old service that we want its port.
|
390
|
+
# It will then forward that request to the
|
391
|
+
# user and retry.
|
392
|
+
unless did_collision then
|
393
|
+
DRbObject.new(nil, uri).collision
|
394
|
+
did_collision = true
|
395
|
+
end
|
396
|
+
sleep(10)
|
397
|
+
retry
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
return true
|
402
|
+
end
|
403
|
+
|
404
|
+
# Deactivates a running Breakpoint service.
|
405
|
+
def deactivate_drb #:nodoc:
|
406
|
+
@service.stop_service unless @service.nil?
|
407
|
+
@service = nil
|
408
|
+
@use_drb = false
|
409
|
+
@drb_service = nil
|
410
|
+
end
|
411
|
+
|
412
|
+
# Returns true when Breakpoints are used over DRb.
|
413
|
+
# Breakpoint.activate_drb causes this to be true.
|
414
|
+
def use_drb? #:nodoc:
|
415
|
+
@use_drb == true
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
module IRB # :nodoc:
|
420
|
+
class << self; remove_method :start; end
|
421
|
+
def self.start(ap_path = nil, main_context = nil, workspace = nil)
|
422
|
+
$0 = File::basename(ap_path, ".rb") if ap_path
|
423
|
+
|
424
|
+
# suppress some warnings about redefined constants
|
425
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
426
|
+
IRB.setup(ap_path)
|
427
|
+
$VERBOSE = old_verbose
|
428
|
+
|
429
|
+
if @CONF[:SCRIPT] then
|
430
|
+
irb = Irb.new(main_context, @CONF[:SCRIPT])
|
431
|
+
else
|
432
|
+
irb = Irb.new(main_context)
|
433
|
+
end
|
434
|
+
|
435
|
+
if workspace then
|
436
|
+
irb.context.workspace = workspace
|
437
|
+
end
|
438
|
+
|
439
|
+
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
|
440
|
+
@CONF[:MAIN_CONTEXT] = irb.context
|
441
|
+
|
442
|
+
old_sigint = trap("SIGINT") do
|
443
|
+
irb.signal_handle
|
444
|
+
end
|
445
|
+
|
446
|
+
catch(:IRB_EXIT) do
|
447
|
+
irb.eval_input
|
448
|
+
end
|
449
|
+
ensure
|
450
|
+
trap("SIGINT", old_sigint)
|
451
|
+
end
|
452
|
+
|
453
|
+
class << self
|
454
|
+
alias :old_CurrentContext :CurrentContext
|
455
|
+
remove_method :CurrentContext
|
456
|
+
end
|
457
|
+
def IRB.CurrentContext
|
458
|
+
if old_CurrentContext.nil? and Breakpoint.use_drb? then
|
459
|
+
result = Object.new
|
460
|
+
def result.last_value; end
|
461
|
+
return result
|
462
|
+
else
|
463
|
+
old_CurrentContext
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
class Context#:nodoc:
|
468
|
+
alias :old_evaluate :evaluate
|
469
|
+
def evaluate(line, line_no)
|
470
|
+
if line.chomp == "exit" then
|
471
|
+
exit
|
472
|
+
else
|
473
|
+
old_evaluate(line, line_no)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
class WorkSpace#:nodoc:
|
479
|
+
alias :old_evaluate :evaluate
|
480
|
+
|
481
|
+
def evaluate(*args)
|
482
|
+
if Breakpoint.use_drb? then
|
483
|
+
result = old_evaluate(*args)
|
484
|
+
if args[0] != :no_proxy and
|
485
|
+
not [true, false, nil].include?(result)
|
486
|
+
then
|
487
|
+
result.extend(DRbUndumped) rescue nil
|
488
|
+
end
|
489
|
+
return result
|
490
|
+
else
|
491
|
+
old_evaluate(*args)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
module InputCompletor#:nodoc:
|
497
|
+
def self.eval(code, context, *more)
|
498
|
+
# Big hack, this assumes that InputCompletor
|
499
|
+
# will only call eval() when it wants code
|
500
|
+
# to be executed in the IRB context.
|
501
|
+
IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
module DRb # :nodoc:
|
507
|
+
class DRbObject#:nodoc:
|
508
|
+
undef :inspect
|
509
|
+
undef :clone
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
# See Breakpoint.breakpoint
|
514
|
+
def breakpoint(id = nil, &block)
|
515
|
+
Binding.of_caller do |context|
|
516
|
+
Breakpoint.breakpoint(id, context, &block)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
# See Breakpoint.assert
|
521
|
+
def assert(&block)
|
522
|
+
Binding.of_caller do |context|
|
523
|
+
Breakpoint.assert(context, &block)
|
524
|
+
end
|
525
|
+
end
|