nitro 0.6.0 → 0.7.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.
Files changed (59) hide show
  1. data/ChangeLog +175 -0
  2. data/README +41 -7
  3. data/RELEASES +24 -0
  4. data/Rakefile +5 -7
  5. data/bin/new_app.rb +26 -4
  6. data/bin/new_form.rb +54 -0
  7. data/bin/proto/config.rb +3 -3
  8. data/bin/proto/root/index.xhtml +2 -34
  9. data/bin/proto/root/style.css +4 -70
  10. data/bin/proto/root/style.xsl +8 -39
  11. data/doc/tutorial.txt +5 -0
  12. data/examples/blog/app.rb +2 -1
  13. data/examples/blog/config.rb +7 -2
  14. data/examples/blog/root/style.xsl +1 -2
  15. data/examples/flash/README +34 -0
  16. data/examples/flash/app.rb +20 -0
  17. data/examples/flash/config.rb +38 -0
  18. data/examples/flash/lib/flash.rb +40 -0
  19. data/examples/flash/root/index.xhtml +25 -0
  20. data/examples/flash/root/show_inline_text.xhtml +12 -0
  21. data/examples/flash/tmp.swf +0 -0
  22. data/examples/og/README +7 -0
  23. data/examples/og/mock_example.rb +58 -0
  24. data/examples/og/run.rb +9 -5
  25. data/examples/tiny/root/include.xhtml +3 -0
  26. data/examples/tiny/root/index.xhtml +2 -1
  27. data/lib/glue/property.rb +166 -107
  28. data/lib/glue/property.rb.old +307 -0
  29. data/lib/nitro/builders/form.rb +26 -17
  30. data/lib/nitro/events.rb +1 -1
  31. data/lib/nitro/markup.rb +120 -0
  32. data/lib/nitro/server/cookie.rb +1 -1
  33. data/lib/nitro/server/dispatcher.rb +5 -6
  34. data/lib/nitro/server/filters.rb +1 -1
  35. data/lib/nitro/server/render.rb +33 -29
  36. data/lib/nitro/server/shaders.rb +32 -3
  37. data/lib/nitro/server/user.rb +1 -1
  38. data/lib/nitro/server/webrick.rb +9 -4
  39. data/lib/nitro/ui/popup.rb +1 -1
  40. data/lib/nitro/ui/select.rb +1 -1
  41. data/lib/nitro/ui/tabs.rb +1 -1
  42. data/lib/nitro/version.rb +2 -2
  43. data/lib/og.rb +17 -6
  44. data/lib/og/backend.rb +34 -4
  45. data/lib/og/backends/mysql.rb +3 -17
  46. data/lib/og/backends/psql.rb +5 -17
  47. data/lib/og/meta.rb +41 -26
  48. data/lib/og/mock.rb +223 -0
  49. data/lib/og/version.rb +2 -2
  50. data/lib/parts/content.rb +61 -0
  51. data/test/glue/{tc_properties.rb → tc_property.rb} +0 -1
  52. data/test/glue/tc_property_mixins.rb +62 -0
  53. data/test/og/tc_lifecycle.rb +107 -0
  54. data/test/tc_og.rb +31 -4
  55. data/vendor/README +6 -0
  56. data/vendor/binding_of_caller.rb +81 -0
  57. data/vendor/breakpoint.rb +526 -0
  58. data/vendor/breakpoint_client.rb +157 -0
  59. metadata +135 -95
data/lib/og/version.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  # * George Moschovitis <gm@navel.gr>
3
3
  #
4
4
  # (c) 2004 Navel, all rights reserved.
5
- # $Id: version.rb 175 2004-11-26 16:11:27Z gmosx $
5
+ # $Id: version.rb 198 2004-12-22 11:26:59Z gmosx $
6
6
 
7
7
  # The version of Og.
8
- $og_version = '0.5.0'
8
+ $og_version = '0.7.0'
@@ -0,0 +1,61 @@
1
+ # = Content
2
+ #
3
+ # Content Management Foundation
4
+ #
5
+ # code:
6
+ # * George Moschovitis <gm@navel.gr>
7
+ #
8
+ # (c) 2004 Navel, all rights reserved.
9
+ # $Id$
10
+
11
+ require 'nitro/markup'
12
+
13
+ module N
14
+
15
+ # = BaseContent
16
+ #
17
+ # The foundamental Content Unit.
18
+ #
19
+ module BaseContent
20
+ include N::Markup
21
+
22
+ prop_accessor :title, String
23
+ prop_accessor :body, String, :markup => true, :ui => :textarea
24
+ end
25
+
26
+ # = Content
27
+ #
28
+ # The foundamental Content Unit.
29
+ #
30
+ #--
31
+ # THINK: rename this to Entity?
32
+ #++
33
+ module Content
34
+ include N::BaseContent
35
+ prop_accessor :ctime, Time
36
+ prop_accessor :mtime, Time
37
+
38
+ def initialize(*args)
39
+ @ctime = @mtime = Time.now
40
+ end
41
+ end
42
+
43
+ # = Category
44
+ #
45
+ class Category
46
+ include N::BaseContent
47
+ end
48
+
49
+ # = ACL
50
+ #
51
+ # Unix style permissions.
52
+ #
53
+ module ACL
54
+ # bits:
55
+ # 0, 1, 2: owner read, write, execute
56
+ # 3, 4, 5: group read, write, execute
57
+ # 6, 7, 8: other read, write, execute
58
+ prop_accessor :permissions
59
+ end
60
+
61
+ end # module
@@ -30,7 +30,6 @@ class SubMsg < Msg
30
30
  # to avoid conflicts with tc_og.rb
31
31
  include Og::Unmanageable
32
32
 
33
- inherit_meta(superclass)
34
33
  # duplicate definition with different type!
35
34
  prop_accessor :count, Float
36
35
  end
@@ -0,0 +1,62 @@
1
+ #! /usr/bin/env ruby
2
+ # vim:sw=2:ai
3
+
4
+ # code:
5
+ # * Thomas Quas <tquas@yahoo.com>
6
+ # * George Moschovitis <gm@navel.gr>
7
+ #
8
+ # $Id$
9
+
10
+ $LOAD_PATH.unshift 'lib'
11
+
12
+ require "test/unit"
13
+ require 'glue/property'
14
+
15
+ module Mixin
16
+ prop_accessor :date
17
+ end
18
+
19
+ class MixedOnly
20
+ include Mixin
21
+ end
22
+
23
+ class MixedWithProp
24
+ include Mixin
25
+
26
+ prop_accessor :dummy
27
+ end
28
+
29
+ class Pure
30
+ prop_accessor :dummy
31
+ end
32
+
33
+ class Empty
34
+ end
35
+
36
+
37
+ # Tests auto management.
38
+ class TC_MixinsTest < ::Test::Unit::TestCase
39
+ def test_the_unmanaged
40
+ assert( Empty.respond_to?( :__props ) == false )
41
+ assert_respond_to( Pure.new, :dummy )
42
+ end
43
+
44
+ def test_manage_entities
45
+ assert_respond_to( Pure, :__props )
46
+ assert_respond_to( Pure.new, :dummy )
47
+ end
48
+
49
+ def test_managing_mixedonly_classes
50
+ assert_respond_to( MixedOnly.new, :date )
51
+ end
52
+
53
+ def test_managing_mixins
54
+ assert_respond_to( Mixin, :__props )
55
+ end
56
+
57
+ def test_managing_mixed_classes
58
+ obj = MixedWithProp.new
59
+ assert( obj.respond_to?( :date ) )
60
+ assert( obj.respond_to?( :dummy ) )
61
+ end
62
+ end
@@ -0,0 +1,107 @@
1
+ #! /usr/bin/env ruby
2
+ # vim:sw=2:ai
3
+
4
+ # code:
5
+ # * Thomas Quas <tquas@yahoo.com>
6
+ # * George Moschovitis <gm@navel.gr>
7
+ #
8
+ # $Id$
9
+
10
+ $LOAD_PATH.unshift 'lib'
11
+
12
+ require "test/unit"
13
+ require 'glue/logger'
14
+ require 'og/mock'
15
+
16
+ $DBG = false
17
+ $log = Logger.new( STDERR ) unless $log
18
+
19
+ class Dummy
20
+ prop_accessor :date
21
+ attr_reader :call_stack
22
+
23
+ def og_pre_insert( oid )
24
+ @call_stack << "pre_insert"
25
+ end
26
+
27
+ def og_post_insert( oid )
28
+ @call_stack << "post_insert"
29
+ end
30
+
31
+ def og_pre_update( oid )
32
+ @call_stack << "pre_update"
33
+ end
34
+
35
+ def og_post_update( oid )
36
+ @call_stack << "post_update"
37
+ end
38
+
39
+ def self.og_pre_delete( conn, oid )
40
+ raise "undeletable"
41
+ end
42
+
43
+ def initialize
44
+ @call_stack = []
45
+ end
46
+ end
47
+
48
+ # Tests the Og managed objects lifecycle.
49
+ class TC_CallbackTest < ::Test::Unit::TestCase
50
+ def test_insert
51
+ obj = Dummy.new
52
+ obj.save!
53
+
54
+ assert( obj.call_stack.shift == "pre_insert" )
55
+ assert( obj.call_stack.shift == "post_insert" )
56
+ assert( obj.call_stack.empty? )
57
+ end
58
+
59
+ def test_update
60
+ obj = Dummy.new
61
+ obj.save!
62
+ obj.call_stack.shift
63
+ obj.call_stack.shift
64
+
65
+ obj.date = Time.now
66
+ obj.save
67
+ assert( obj.call_stack.shift == "pre_update" )
68
+ assert( obj.call_stack.shift == "post_update" )
69
+ assert( obj.call_stack.empty? )
70
+ end
71
+
72
+ def test_delete
73
+ obj = Dummy.new
74
+ obj.save!
75
+ obj.call_stack.shift
76
+ obj.call_stack.shift
77
+
78
+ assert_raise( RuntimeError, "undeletable" ) { obj.delete! }
79
+ end
80
+
81
+ def setup
82
+ psql = true
83
+
84
+ if psql
85
+ config = {
86
+ :backend => "psql",
87
+ :address => "localhost",
88
+ :database => "test",
89
+ :user => "postgres",
90
+ :password => "navelrulez",
91
+ :connection_count => 1
92
+ }
93
+ else
94
+ config = {
95
+ :backend => "mysql",
96
+ :address => "localhost",
97
+ :database => "test",
98
+ :user => "root",
99
+ :password => "navelrulez",
100
+ :connection_count => 1
101
+ }
102
+ end
103
+ Og::Database.drop_db!( config )
104
+ $og = Og::Database.new( config )
105
+ $og.get_connection()
106
+ end
107
+ end
data/test/tc_og.rb CHANGED
@@ -46,7 +46,7 @@ class Article
46
46
  end
47
47
 
48
48
  class Comment
49
- belongs_to :article, Test::Article
49
+ belongs_to :article, Test::Article, :extra_sql => 'NOT NULL'
50
50
  belongs_to :author, Test::User
51
51
  prop_accessor :body, String
52
52
  prop_accessor :create_time, Time
@@ -65,6 +65,14 @@ class Comment
65
65
  end
66
66
  end
67
67
 
68
+ module MyMixin
69
+ prop_accessor :title, String
70
+ end
71
+
72
+ class MyClass
73
+ include Test::MyMixin
74
+ end
75
+
68
76
  class TC_N_OG < Test::Unit::TestCase
69
77
 
70
78
  def setup
@@ -148,16 +156,17 @@ class TC_N_OG < Test::Unit::TestCase
148
156
 
149
157
  article = Article.new("Title", "Body")
150
158
  article.save!
151
-
159
+
152
160
  comment = Comment.new("This is a comment")
153
161
  comment.article = article
154
162
  comment.author = User["gmosx"]
155
163
  comment.save!
156
164
 
165
+
166
+ # test automatically generated add_commnet
157
167
  comment = Comment.new("This is another comment")
158
- comment.article = article.oid
159
168
  comment.author = User["gmosx"]
160
- comment.save!
169
+ article.add_comment(comment)
161
170
 
162
171
  assert_equal(article.oid, comment.article_oid)
163
172
 
@@ -172,6 +181,24 @@ class TC_N_OG < Test::Unit::TestCase
172
181
  assert_equal(nil, Comment.all)
173
182
 
174
183
  comment.delete!
184
+
185
+ # test extra_sql
186
+
187
+ for p in Comment.__props
188
+ if :article_oid == p.symbol
189
+ assert_equal('NOT NULL', p.meta[:extra_sql])
190
+ break
191
+ end
192
+ end
193
+
194
+ assert($og.managed_classes.include?(Test::Article))
195
+
196
+ # bug: indirectly managed (includes managed Module)
197
+ assert($og.managed_classes.include?(Test::MyClass))
198
+
199
+ # test create
200
+ article = Article.create("title", "body")
201
+ assert_equal(3, article.oid)
175
202
  end
176
203
 
177
204
  end
data/vendor/README ADDED
@@ -0,0 +1,6 @@
1
+ Libraries from other vendors belong here.
2
+
3
+ extensions
4
+ breakpointer
5
+ dev-utils
6
+ postgres-pr
@@ -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,526 @@
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'
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
118
+ # Proxy to a Breakpoint client. Lets you directly execute code
119
+ # in the context of the client.
120
+ class Client
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) rescue nil
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
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'],
363
+ ignore_collisions = false)
364
+
365
+ return false if @use_drb
366
+
367
+ uri ||= 'druby://localhost:42531'
368
+
369
+ if allowed_hosts then
370
+ acl = ["deny", "all"]
371
+
372
+ Array(allowed_hosts).each do |host|
373
+ acl += ["allow", host]
374
+ end
375
+
376
+ DRb.install_acl(ACL.new(acl))
377
+ end
378
+
379
+ @use_drb = true
380
+ @drb_service = DRbService.new
381
+ did_collision = false
382
+ begin
383
+ @service = DRb.start_service(uri, @drb_service)
384
+ rescue Errno::EADDRINUSE
385
+ if ignore_collisions then
386
+ nil
387
+ else
388
+ # The port is already occupied by another
389
+ # Breakpoint service. We will try to tell
390
+ # the old service that we want its port.
391
+ # It will then forward that request to the
392
+ # user and retry.
393
+ unless did_collision then
394
+ DRbObject.new(nil, uri).collision
395
+ did_collision = true
396
+ end
397
+ sleep(10)
398
+ retry
399
+ end
400
+ end
401
+
402
+ return true
403
+ end
404
+
405
+ # Deactivates a running Breakpoint service.
406
+ def deactivate_drb
407
+ @service.stop_service unless @service.nil?
408
+ @service = nil
409
+ @use_drb = false
410
+ @drb_service = nil
411
+ end
412
+
413
+ # Returns true when Breakpoints are used over DRb.
414
+ # Breakpoint.activate_drb causes this to be true.
415
+ def use_drb?
416
+ @use_drb == true
417
+ end
418
+ end
419
+
420
+ module IRB # :nodoc:
421
+ class << self; remove_method :start; end
422
+ def self.start(ap_path = nil, main_context = nil, workspace = nil)
423
+ $0 = File::basename(ap_path, ".rb") if ap_path
424
+
425
+ # suppress some warnings about redefined constants
426
+ old_verbose, $VERBOSE = $VERBOSE, nil
427
+ IRB.setup(ap_path)
428
+ $VERBOSE = old_verbose
429
+
430
+ if @CONF[:SCRIPT] then
431
+ irb = Irb.new(main_context, @CONF[:SCRIPT])
432
+ else
433
+ irb = Irb.new(main_context)
434
+ end
435
+
436
+ if workspace then
437
+ irb.context.workspace = workspace
438
+ end
439
+
440
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
441
+ @CONF[:MAIN_CONTEXT] = irb.context
442
+
443
+ old_sigint = trap("SIGINT") do
444
+ irb.signal_handle
445
+ end
446
+
447
+ catch(:IRB_EXIT) do
448
+ irb.eval_input
449
+ end
450
+ ensure
451
+ trap("SIGINT", old_sigint)
452
+ end
453
+
454
+ class << self
455
+ alias :old_CurrentContext :CurrentContext
456
+ remove_method :CurrentContext
457
+ end
458
+ def IRB.CurrentContext
459
+ if old_CurrentContext.nil? and Breakpoint.use_drb? then
460
+ result = Object.new
461
+ def result.last_value; end
462
+ return result
463
+ else
464
+ old_CurrentContext
465
+ end
466
+ end
467
+
468
+ class Context
469
+ alias :old_evaluate :evaluate
470
+ def evaluate(line, line_no)
471
+ if line.chomp == "exit" then
472
+ exit
473
+ else
474
+ old_evaluate(line, line_no)
475
+ end
476
+ end
477
+ end
478
+
479
+ class WorkSpace
480
+ alias :old_evaluate :evaluate
481
+
482
+ def evaluate(*args)
483
+ if Breakpoint.use_drb? then
484
+ result = old_evaluate(*args)
485
+ if args[0] != :no_proxy and
486
+ not [true, false, nil].include?(result)
487
+ then
488
+ result.extend(DRbUndumped) if result
489
+ end
490
+ return result
491
+ else
492
+ old_evaluate(*args)
493
+ end
494
+ end
495
+ end
496
+
497
+ module InputCompletor
498
+ def self.eval(code, context, *more)
499
+ # Big hack, this assumes that InputCompletor
500
+ # will only call eval() when it wants code
501
+ # to be executed in the IRB context.
502
+ IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
503
+ end
504
+ end
505
+ end
506
+
507
+ module DRb # :nodoc:
508
+ class DRbObject
509
+ undef :inspect
510
+ undef :clone
511
+ end
512
+ end
513
+
514
+ # See Breakpoint.breakpoint
515
+ def breakpoint(id = nil, &block)
516
+ Binding.of_caller do |context|
517
+ Breakpoint.breakpoint(id, context, &block)
518
+ end
519
+ end
520
+
521
+ # See Breakpoint.assert
522
+ def assert(&block)
523
+ Binding.of_caller do |context|
524
+ Breakpoint.assert(context, &block)
525
+ end
526
+ end