drnic-liquid 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/CHANGELOG +44 -0
  2. data/History.txt +44 -0
  3. data/MIT-LICENSE +20 -0
  4. data/Manifest.txt +34 -0
  5. data/README.rdoc +44 -0
  6. data/Rakefile +30 -0
  7. data/example/server/example_servlet.rb +37 -0
  8. data/example/server/liquid_servlet.rb +28 -0
  9. data/example/server/server.rb +12 -0
  10. data/example/server/templates/index.liquid +6 -0
  11. data/example/server/templates/products.liquid +45 -0
  12. data/init.rb +8 -0
  13. data/lib/extras/liquid_view.rb +51 -0
  14. data/lib/liquid.rb +70 -0
  15. data/lib/liquid/block.rb +102 -0
  16. data/lib/liquid/condition.rb +120 -0
  17. data/lib/liquid/context.rb +221 -0
  18. data/lib/liquid/document.rb +17 -0
  19. data/lib/liquid/drop.rb +51 -0
  20. data/lib/liquid/errors.rb +11 -0
  21. data/lib/liquid/extensions.rb +56 -0
  22. data/lib/liquid/file_system.rb +62 -0
  23. data/lib/liquid/htmltags.rb +74 -0
  24. data/lib/liquid/module_ex.rb +62 -0
  25. data/lib/liquid/standardfilters.rb +209 -0
  26. data/lib/liquid/strainer.rb +51 -0
  27. data/lib/liquid/tag.rb +26 -0
  28. data/lib/liquid/tags/assign.rb +33 -0
  29. data/lib/liquid/tags/capture.rb +35 -0
  30. data/lib/liquid/tags/case.rb +83 -0
  31. data/lib/liquid/tags/comment.rb +9 -0
  32. data/lib/liquid/tags/cycle.rb +59 -0
  33. data/lib/liquid/tags/for.rb +136 -0
  34. data/lib/liquid/tags/if.rb +79 -0
  35. data/lib/liquid/tags/ifchanged.rb +20 -0
  36. data/lib/liquid/tags/include.rb +55 -0
  37. data/lib/liquid/tags/unless.rb +33 -0
  38. data/lib/liquid/template.rb +147 -0
  39. data/lib/liquid/variable.rb +49 -0
  40. data/liquid.gemspec +40 -0
  41. data/performance/shopify.rb +92 -0
  42. data/performance/shopify/comment_form.rb +33 -0
  43. data/performance/shopify/database.rb +45 -0
  44. data/performance/shopify/json_filter.rb +7 -0
  45. data/performance/shopify/liquid.rb +18 -0
  46. data/performance/shopify/money_filter.rb +18 -0
  47. data/performance/shopify/paginate.rb +93 -0
  48. data/performance/shopify/shop_filter.rb +98 -0
  49. data/performance/shopify/tag_filter.rb +25 -0
  50. data/performance/shopify/vision.database.yml +945 -0
  51. data/performance/shopify/weight_filter.rb +11 -0
  52. data/performance/tests/dropify/article.liquid +74 -0
  53. data/performance/tests/dropify/blog.liquid +33 -0
  54. data/performance/tests/dropify/cart.liquid +66 -0
  55. data/performance/tests/dropify/collection.liquid +22 -0
  56. data/performance/tests/dropify/index.liquid +47 -0
  57. data/performance/tests/dropify/page.liquid +8 -0
  58. data/performance/tests/dropify/product.liquid +68 -0
  59. data/performance/tests/dropify/theme.liquid +105 -0
  60. data/performance/tests/ripen/article.liquid +74 -0
  61. data/performance/tests/ripen/blog.liquid +13 -0
  62. data/performance/tests/ripen/cart.liquid +54 -0
  63. data/performance/tests/ripen/collection.liquid +29 -0
  64. data/performance/tests/ripen/index.liquid +32 -0
  65. data/performance/tests/ripen/page.liquid +4 -0
  66. data/performance/tests/ripen/product.liquid +75 -0
  67. data/performance/tests/ripen/theme.liquid +85 -0
  68. data/performance/tests/tribble/404.liquid +56 -0
  69. data/performance/tests/tribble/article.liquid +98 -0
  70. data/performance/tests/tribble/blog.liquid +41 -0
  71. data/performance/tests/tribble/cart.liquid +134 -0
  72. data/performance/tests/tribble/collection.liquid +70 -0
  73. data/performance/tests/tribble/index.liquid +94 -0
  74. data/performance/tests/tribble/page.liquid +56 -0
  75. data/performance/tests/tribble/product.liquid +116 -0
  76. data/performance/tests/tribble/search.liquid +51 -0
  77. data/performance/tests/tribble/theme.liquid +90 -0
  78. data/performance/tests/vogue/article.liquid +66 -0
  79. data/performance/tests/vogue/blog.liquid +32 -0
  80. data/performance/tests/vogue/cart.liquid +58 -0
  81. data/performance/tests/vogue/collection.liquid +19 -0
  82. data/performance/tests/vogue/index.liquid +22 -0
  83. data/performance/tests/vogue/page.liquid +3 -0
  84. data/performance/tests/vogue/product.liquid +62 -0
  85. data/performance/tests/vogue/theme.liquid +122 -0
  86. data/test/assign_test.rb +11 -0
  87. data/test/block_test.rb +58 -0
  88. data/test/condition_test.rb +109 -0
  89. data/test/context_test.rb +482 -0
  90. data/test/drop_test.rb +162 -0
  91. data/test/error_handling_test.rb +89 -0
  92. data/test/extra/breakpoint.rb +547 -0
  93. data/test/extra/caller.rb +80 -0
  94. data/test/file_system_test.rb +30 -0
  95. data/test/filter_test.rb +95 -0
  96. data/test/helper.rb +20 -0
  97. data/test/html_tag_test.rb +31 -0
  98. data/test/if_else_test.rb +131 -0
  99. data/test/include_tag_test.rb +115 -0
  100. data/test/module_ex_test.rb +89 -0
  101. data/test/output_test.rb +121 -0
  102. data/test/parsing_quirks_test.rb +41 -0
  103. data/test/regexp_test.rb +45 -0
  104. data/test/security_test.rb +41 -0
  105. data/test/standard_filter_test.rb +161 -0
  106. data/test/standard_tag_test.rb +400 -0
  107. data/test/statements_test.rb +137 -0
  108. data/test/strainer_test.rb +21 -0
  109. data/test/template_test.rb +26 -0
  110. data/test/test_helper.rb +20 -0
  111. data/test/unless_else_test.rb +27 -0
  112. data/test/variable_test.rb +172 -0
  113. metadata +187 -0
@@ -0,0 +1,162 @@
1
+
2
+ #!/usr/bin/env ruby
3
+ require File.dirname(__FILE__) + '/helper'
4
+
5
+ class ContextDrop < Liquid::Drop
6
+ def scopes
7
+ @context.scopes.size
8
+ end
9
+
10
+ def scopes_as_array
11
+ (1..@context.scopes.size).to_a
12
+ end
13
+
14
+ def loop_pos
15
+ @context['forloop.index']
16
+ end
17
+
18
+ def break
19
+ Breakpoint.breakpoint
20
+ end
21
+
22
+ def before_method(method)
23
+ return @context[method]
24
+ end
25
+ end
26
+
27
+
28
+ class ProductDrop < Liquid::Drop
29
+
30
+ class TextDrop < Liquid::Drop
31
+ def array
32
+ ['text1', 'text2']
33
+ end
34
+
35
+ def text
36
+ 'text1'
37
+ end
38
+ end
39
+
40
+ class CatchallDrop < Liquid::Drop
41
+ def before_method(method)
42
+ return 'method: ' << method
43
+ end
44
+ end
45
+
46
+ def texts
47
+ TextDrop.new
48
+ end
49
+
50
+ def catchall
51
+ CatchallDrop.new
52
+ end
53
+
54
+ def context
55
+ ContextDrop.new
56
+ end
57
+
58
+ protected
59
+ def callmenot
60
+ "protected"
61
+ end
62
+ end
63
+
64
+ class EnumerableDrop < Liquid::Drop
65
+
66
+ def size
67
+ 3
68
+ end
69
+
70
+ def each
71
+ yield 1
72
+ yield 2
73
+ yield 3
74
+ end
75
+ end
76
+
77
+
78
+ class DropsTest < Test::Unit::TestCase
79
+ include Liquid
80
+
81
+ def test_product_drop
82
+
83
+ assert_nothing_raised do
84
+ tpl = Liquid::Template.parse( ' ' )
85
+ tpl.render('product' => ProductDrop.new)
86
+ end
87
+ end
88
+
89
+ def test_text_drop
90
+ output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
91
+ assert_equal ' text1 ', output
92
+
93
+ end
94
+
95
+ def test_text_drop
96
+ output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
97
+ assert_equal ' method: unknown ', output
98
+
99
+ end
100
+
101
+ def test_text_array_drop
102
+ output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% end %}' ).render('product' => ProductDrop.new)
103
+ assert_equal ' text1 text2 ', output
104
+ end
105
+
106
+ def test_context_drop
107
+ output = Liquid::Template.parse( ' {{ context.bar }} ' ).render('context' => ContextDrop.new, 'bar' => "carrot")
108
+ assert_equal ' carrot ', output
109
+ end
110
+
111
+ def test_nested_context_drop
112
+ output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render('product' => ProductDrop.new, 'foo' => "monkey")
113
+ assert_equal ' monkey ', output
114
+ end
115
+
116
+ def test_protected
117
+ output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render('product' => ProductDrop.new)
118
+ assert_equal ' ', output
119
+ end
120
+
121
+ def test_scope
122
+ assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
123
+ assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%end%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
124
+ assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%end%}{%end%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
125
+ end
126
+
127
+ def test_scope_though_proc
128
+ assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
129
+ assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%end%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
130
+ assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%end%}{%end%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
131
+ end
132
+
133
+ def test_scope_with_assigns
134
+ assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render('context' => ContextDrop.new)
135
+ assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%end%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
136
+ assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render('context' => ContextDrop.new)
137
+ assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render('context' => ContextDrop.new)
138
+ end
139
+
140
+ def test_scope_from_tags
141
+ assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% end %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
142
+ assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% end %}{% end %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
143
+ assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% end %}{% end %}{% end %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
144
+ end
145
+
146
+ def test_access_context_from_drop
147
+ assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% end %}' ).render('context' => ContextDrop.new, 'dummy' => [1,2,3])
148
+ end
149
+
150
+ def test_enumerable_drop
151
+ assert_equal '123', Liquid::Template.parse( '{% for c in collection %}{{c}}{% end %}').render('collection' => EnumerableDrop.new)
152
+ end
153
+
154
+ def test_enumerable_drop_size
155
+ assert_equal '3', Liquid::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
156
+ end
157
+
158
+
159
+
160
+ end
161
+
162
+
@@ -0,0 +1,89 @@
1
+
2
+ #!/usr/bin/env ruby
3
+ require File.dirname(__FILE__) + '/helper'
4
+
5
+ class ErrorDrop < Liquid::Drop
6
+ def standard_error
7
+ raise Liquid::StandardError, 'standard error'
8
+ end
9
+
10
+ def argument_error
11
+ raise Liquid::ArgumentError, 'argument error'
12
+ end
13
+
14
+ def syntax_error
15
+ raise Liquid::SyntaxError, 'syntax error'
16
+ end
17
+
18
+ end
19
+
20
+
21
+ class ErrorHandlingTest < Test::Unit::TestCase
22
+ include Liquid
23
+
24
+ def test_standard_error
25
+ assert_nothing_raised do
26
+ template = Liquid::Template.parse( ' {{ errors.standard_error }} ' )
27
+ assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
28
+
29
+ assert_equal 1, template.errors.size
30
+ assert_equal StandardError, template.errors.first.class
31
+ end
32
+ end
33
+
34
+ def test_syntax
35
+
36
+ assert_nothing_raised do
37
+
38
+ template = Liquid::Template.parse( ' {{ errors.syntax_error }} ' )
39
+ assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
40
+
41
+ assert_equal 1, template.errors.size
42
+ assert_equal SyntaxError, template.errors.first.class
43
+
44
+ end
45
+
46
+ end
47
+
48
+ def test_argument
49
+
50
+ assert_nothing_raised do
51
+
52
+ template = Liquid::Template.parse( ' {{ errors.argument_error }} ' )
53
+ assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
54
+
55
+ assert_equal 1, template.errors.size
56
+ assert_equal ArgumentError, template.errors.first.class
57
+
58
+ end
59
+
60
+ end
61
+
62
+ def test_missing_endtag_parse_time_error
63
+
64
+ assert_raise(Liquid::SyntaxError) do
65
+
66
+ template = Liquid::Template.parse(' {% for a in b %} ... ')
67
+
68
+ end
69
+
70
+ end
71
+
72
+
73
+ def test_unrecognized_operator
74
+
75
+ assert_nothing_raised do
76
+
77
+ template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% end %} ')
78
+ assert_equal ' Liquid error: Unknown operator =! ', template.render
79
+
80
+ assert_equal 1, template.errors.size
81
+ assert_equal Liquid::ArgumentError, template.errors.first.class
82
+
83
+ end
84
+
85
+ end
86
+
87
+ end
88
+
89
+
@@ -0,0 +1,547 @@
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 'caller'
20
+ require 'drb'
21
+ require 'drb/acl'
22
+ require 'thread'
23
+
24
+ module Breakpoint
25
+ id = %q$Id: breakpoint.rb 52 2005-02-26 19:43:19Z flgr $
26
+ current_version = id.split(" ")[2]
27
+ unless defined?(Version)
28
+ # The Version of ruby-breakpoint you are using as String of the
29
+ # 1.2.3 form where the digits stand for release, major and minor
30
+ # version respectively.
31
+ Version = "0.5.0"
32
+ end
33
+
34
+ extend self
35
+
36
+ # This will pop up an interactive ruby session at a
37
+ # pre-defined break point in a Ruby application. In
38
+ # this session you can examine the environment of
39
+ # the break point.
40
+ #
41
+ # You can get a list of variables in the context using
42
+ # local_variables via +local_variables+. You can then
43
+ # examine their values by typing their names.
44
+ #
45
+ # You can have a look at the call stack via +caller+.
46
+ #
47
+ # The source code around the location where the breakpoint
48
+ # was executed can be examined via +source_lines+. Its
49
+ # argument specifies how much lines of context to display.
50
+ # The default amount of context is 5 lines. Note that
51
+ # the call to +source_lines+ can raise an exception when
52
+ # it isn't able to read in the source code.
53
+ #
54
+ # breakpoints can also return a value. They will execute
55
+ # a supplied block for getting a default return value.
56
+ # A custom value can be returned from the session by doing
57
+ # +throw(:debug_return, value)+.
58
+ #
59
+ # You can also give names to break points which will be
60
+ # used in the message that is displayed upon execution
61
+ # of them.
62
+ #
63
+ # Here's a sample of how breakpoints should be placed:
64
+ #
65
+ # class Person
66
+ # def initialize(name, age)
67
+ # @name, @age = name, age
68
+ # breakpoint("Person#initialize")
69
+ # end
70
+ #
71
+ # attr_reader :age
72
+ # def name
73
+ # breakpoint("Person#name") { @name }
74
+ # end
75
+ # end
76
+ #
77
+ # person = Person.new("Random Person", 23)
78
+ # puts "Name: #{person.name}"
79
+ #
80
+ # And here is a sample debug session:
81
+ #
82
+ # Executing break point "Person#initialize" at file.rb:4 in `initialize'
83
+ # irb(#<Person:0x292fbe8>):001:0> local_variables
84
+ # => ["name", "age", "_", "__"]
85
+ # irb(#<Person:0x292fbe8>):002:0> [name, age]
86
+ # => ["Random Person", 23]
87
+ # irb(#<Person:0x292fbe8>):003:0> [@name, @age]
88
+ # => ["Random Person", 23]
89
+ # irb(#<Person:0x292fbe8>):004:0> self
90
+ # => #<Person:0x292fbe8 @age=23, @name="Random Person">
91
+ # irb(#<Person:0x292fbe8>):005:0> @age += 1; self
92
+ # => #<Person:0x292fbe8 @age=24, @name="Random Person">
93
+ # irb(#<Person:0x292fbe8>):006:0> exit
94
+ # Executing break point "Person#name" at file.rb:9 in `name'
95
+ # irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
96
+ # Name: Overriden name
97
+ #
98
+ # Breakpoint sessions will automatically have a few
99
+ # convenience methods available. See Breakpoint::CommandBundle
100
+ # for a list of them.
101
+ #
102
+ # Breakpoints can also be used remotely over sockets.
103
+ # This is implemented by running part of the IRB session
104
+ # in the application and part of it in a special client.
105
+ # You have to call Breakpoint.activate_drb to enable
106
+ # support for remote breakpoints and then run
107
+ # breakpoint_client.rb which is distributed with this
108
+ # library. See the documentation of Breakpoint.activate_drb
109
+ # for details.
110
+ def breakpoint(id = nil, context = nil, &block)
111
+ callstack = caller
112
+ callstack.slice!(0, 3) if callstack.first["breakpoint"]
113
+ file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
114
+
115
+ message = "Executing break point " + (id ? "#{id.inspect} " : "") +
116
+ "at #{file}:#{line}" + (method ? " in `#{method}'" : "")
117
+
118
+ if context then
119
+ return handle_breakpoint(context, message, file, line, &block)
120
+ end
121
+
122
+ Binding.of_caller do |binding_context|
123
+ handle_breakpoint(binding_context, message, file, line, &block)
124
+ end
125
+ end
126
+
127
+ # These commands are automatically available in all breakpoint shells.
128
+ module CommandBundle
129
+ # Proxy to a Breakpoint client. Lets you directly execute code
130
+ # in the context of the client.
131
+ class Client
132
+ def initialize(eval_handler) # :nodoc:
133
+ eval_handler.untaint
134
+ @eval_handler = eval_handler
135
+ end
136
+
137
+ instance_methods.each do |method|
138
+ next if method[/^__.+__$/]
139
+ undef_method method
140
+ end
141
+
142
+ # Executes the specified code at the client.
143
+ def eval(code)
144
+ @eval_handler.call(code)
145
+ end
146
+
147
+ # Will execute the specified statement at the client.
148
+ def method_missing(method, *args, &block)
149
+ if args.empty? and not block
150
+ result = eval "#{method}"
151
+ else
152
+ # This is a bit ugly. The alternative would be using an
153
+ # eval context instead of an eval handler for executing
154
+ # the code at the client. The problem with that approach
155
+ # is that we would have to handle special expressions
156
+ # like "self", "nil" or constants ourself which is hard.
157
+ remote = eval %{
158
+ result = lambda { |block, *args| #{method}(*args, &block) }
159
+ def result.call_with_block(*args, &block)
160
+ call(block, *args)
161
+ end
162
+ result
163
+ }
164
+ remote.call_with_block(*args, &block)
165
+ end
166
+
167
+ return result
168
+ end
169
+ end
170
+
171
+ # Returns the source code surrounding the location where the
172
+ # breakpoint was issued.
173
+ def source_lines(context = 5, return_line_numbers = false)
174
+ lines = File.readlines(@__bp_file).map { |line| line.chomp }
175
+
176
+ break_line = @__bp_line
177
+ start_line = [break_line - context, 1].max
178
+ endif = break_line + context
179
+
180
+ result = lines[(start_line - 1) .. (endif - 1)]
181
+
182
+ if return_line_numbers then
183
+ return [start_line, break_line, result]
184
+ else
185
+ return result
186
+ end
187
+ end
188
+
189
+ # Lets an object that will forward method calls to the breakpoint
190
+ # client. This is useful for outputting longer things at the client
191
+ # and so on. You can for example do these things:
192
+ #
193
+ # client.puts "Hello" # outputs "Hello" at client console
194
+ # # outputs "Hello" into the file temp.txt at the client
195
+ # client.File.open("temp.txt", "w") { |f| f.puts "Hello" }
196
+ def client()
197
+ if Breakpoint.use_drb? then
198
+ sleep(0.5) until Breakpoint.drb_service.eval_handler
199
+ Client.new(Breakpoint.drb_service.eval_handler)
200
+ else
201
+ Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
202
+ end
203
+ end
204
+ end
205
+
206
+ def handle_breakpoint(context, message, file = "", line = "", &block) # :nodoc:
207
+ catch(:debug_return) do |value|
208
+ eval(%{
209
+ @__bp_file = #{file.inspect}
210
+ @__bp_line = #{line}
211
+ extend Breakpoint::CommandBundle
212
+ extend DRbUndumped if self
213
+ }, context) rescue nil
214
+
215
+ if not use_drb? then
216
+ puts message
217
+ IRB.start(nil, IRB::WorkSpace.new(context))
218
+ else
219
+ @drb_service.add_breakpoint(context, message)
220
+ end
221
+
222
+ block.call if block
223
+ end
224
+ end
225
+
226
+ # These exceptions will be raised on failed asserts
227
+ # if Breakpoint.asserts_cause_exceptions is set to
228
+ # true.
229
+ class FailedAssertError < RuntimeError
230
+ end
231
+
232
+ # This asserts that the block evaluates to true.
233
+ # If it doesn't evaluate to true a breakpoint will
234
+ # automatically be created at that execution point.
235
+ #
236
+ # You can disable assert checking in production
237
+ # code by setting Breakpoint.optimize_asserts to
238
+ # true. (It will still be enabled when Ruby is run
239
+ # via the -d argument.)
240
+ #
241
+ # Example:
242
+ # person_name = "Foobar"
243
+ # assert { not person_name.nil? }
244
+ #
245
+ # Note: If you want to use this method from an
246
+ # unit test, you will have to call it by its full
247
+ # name, Breakpoint.assert.
248
+ def assert(context = nil, &condition)
249
+ return if Breakpoint.optimize_asserts and not $DEBUG
250
+ return if yield
251
+
252
+ callstack = caller
253
+ callstack.slice!(0, 3) if callstack.first["assert"]
254
+ file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
255
+
256
+ message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."
257
+
258
+ if Breakpoint.asserts_cause_exceptions and not $DEBUG then
259
+ raise(Breakpoint::FailedAssertError, message)
260
+ end
261
+
262
+ message += " Executing implicit breakpoint."
263
+
264
+ if context then
265
+ return handle_breakpoint(context, message, file, line)
266
+ end
267
+
268
+ Binding.of_caller do |context|
269
+ handle_breakpoint(context, message, file, line)
270
+ end
271
+ end
272
+
273
+ # Whether asserts should be ignored if not in debug mode.
274
+ # Debug mode can be enabled by running ruby with the -d
275
+ # switch or by setting $DEBUG to true.
276
+ attr_accessor :optimize_asserts
277
+ self.optimize_asserts = false
278
+
279
+ # Whether an Exception should be raised on failed asserts
280
+ # in non-$DEBUG code or not. By default this is disabled.
281
+ attr_accessor :asserts_cause_exceptions
282
+ self.asserts_cause_exceptions = false
283
+ @use_drb = false
284
+
285
+ attr_reader :drb_service # :nodoc:
286
+
287
+ class DRbService # :nodoc:
288
+ include DRbUndumped
289
+
290
+ def initialize
291
+ @handler = @eval_handler = @collision_handler = nil
292
+
293
+ IRB.instance_eval { @CONF[:RC] = true }
294
+ IRB.run_config
295
+ end
296
+
297
+ def collision
298
+ sleep(0.5) until @collision_handler
299
+
300
+ @collision_handler.untaint
301
+
302
+ @collision_handler.call
303
+ end
304
+
305
+ def ping() end
306
+
307
+ def add_breakpoint(context, message)
308
+ workspace = IRB::WorkSpace.new(context)
309
+ workspace.extend(DRbUndumped)
310
+
311
+ sleep(0.5) until @handler
312
+
313
+ @handler.untaint
314
+ @handler.call(workspace, message)
315
+ rescue Errno::ECONNREFUSED, DRb::DRbConnError
316
+ raise if Breakpoint.use_drb?
317
+ end
318
+
319
+ attr_accessor :handler, :eval_handler, :collision_handler
320
+ end
321
+
322
+ # Will run Breakpoint in DRb mode. This will spawn a server
323
+ # that can be attached to via the breakpoint-client command
324
+ # whenever a breakpoint is executed. This is useful when you
325
+ # are debugging CGI applications or other applications where
326
+ # you can't access debug sessions via the standard input and
327
+ # output of your application.
328
+ #
329
+ # You can specify an URI where the DRb server will run at.
330
+ # This way you can specify the port the server runs on. The
331
+ # default URI is druby://localhost:42531.
332
+ #
333
+ # Please note that breakpoints will be skipped silently in
334
+ # case the DRb server can not spawned. (This can happen if
335
+ # the port is already used by another instance of your
336
+ # application on CGI or another application.)
337
+ #
338
+ # Also note that by default this will only allow access
339
+ # from localhost. You can however specify a list of
340
+ # allowed hosts or nil (to allow access from everywhere).
341
+ # But that will still not protect you from somebody
342
+ # reading the data as it goes through the net.
343
+ #
344
+ # A good approach for getting security and remote access
345
+ # is setting up an SSH tunnel between the DRb service
346
+ # and the client. This is usually done like this:
347
+ #
348
+ # $ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com
349
+ # (This will connect port 20000 at the client side to port
350
+ # 20000 at the server side, and port 10000 at the server
351
+ # side to port 10000 at the client side.)
352
+ #
353
+ # After that do this on the server side: (the code being debugged)
354
+ # Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")
355
+ #
356
+ # And at the client side:
357
+ # ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
358
+ #
359
+ # Running through such a SSH proxy will also let you use
360
+ # breakpoint.rb in case you are behind a firewall.
361
+ #
362
+ # Detailed information about running DRb through firewalls is
363
+ # available at http://www.rubygarden.org/ruby?DrbTutorial
364
+ #
365
+ # == Security considerations
366
+ # Usually you will be fine when using the default druby:// URI and the default
367
+ # access control list. However, if you are sitting on a machine where there are
368
+ # local users that you likely can not trust (this is the case for example on
369
+ # most web hosts which have multiple users sitting on the same physical machine)
370
+ # you will be better off by doing client/server communication through a unix
371
+ # socket. This can be accomplished by calling with a drbunix:/ style URI, e.g.
372
+ # <code>Breakpoint.activate_drb('drbunix:/tmp/breakpoint_server')</code>. This
373
+ # will only work on Unix based platforms.
374
+ def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
375
+ ignore_collisions = false)
376
+
377
+ return false if @use_drb
378
+
379
+ uri ||= 'druby://localhost:42531'
380
+
381
+ if allowed_hosts then
382
+ acl = ["deny", "all"]
383
+
384
+ Array(allowed_hosts).each do |host|
385
+ acl += ["allow", host]
386
+ end
387
+
388
+ DRb.install_acl(ACL.new(acl))
389
+ end
390
+
391
+ @use_drb = true
392
+ @drb_service = DRbService.new
393
+ did_collision = false
394
+ begin
395
+ @service = DRb.start_service(uri, @drb_service)
396
+ rescue Errno::EADDRINUSE
397
+ if ignore_collisions then
398
+ nil
399
+ else
400
+ # The port is already occupied by another
401
+ # Breakpoint service. We will try to tell
402
+ # the old service that we want its port.
403
+ # It will then forward that request to the
404
+ # user and retry.
405
+ unless did_collision then
406
+ DRbObject.new(nil, uri).collision
407
+ did_collision = true
408
+ end
409
+ sleep(10)
410
+ retry
411
+ end
412
+ end
413
+
414
+ return true
415
+ end
416
+
417
+ # Deactivates a running Breakpoint service.
418
+ def deactivate_drb
419
+ Thread.exclusive do
420
+ @service.stop_service unless @service.nil?
421
+ @service = nil
422
+ @use_drb = false
423
+ @drb_service = nil
424
+ end
425
+ end
426
+
427
+ # Returns true when Breakpoints are used over DRb.
428
+ # Breakpoint.activate_drb causes this to be true.
429
+ def use_drb?
430
+ @use_drb == true
431
+ end
432
+ end
433
+
434
+ module IRB # :nodoc:
435
+ class << self; remove_method :start; end
436
+ def self.start(ap_path = nil, main_context = nil, workspace = nil)
437
+ $0 = File::basename(ap_path, ".rb") if ap_path
438
+
439
+ # suppress some warnings about redefined constants
440
+ old_verbose, $VERBOSE = $VERBOSE, nil
441
+ IRB.setup(ap_path)
442
+ $VERBOSE = old_verbose
443
+
444
+ if @CONF[:SCRIPT] then
445
+ irb = Irb.new(main_context, @CONF[:SCRIPT])
446
+ else
447
+ irb = Irb.new(main_context)
448
+ end
449
+
450
+ if workspace then
451
+ irb.context.workspace = workspace
452
+ end
453
+
454
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
455
+ @CONF[:MAIN_CONTEXT] = irb.context
456
+
457
+ old_sigint = trap("SIGINT") do
458
+ begin
459
+ irb.signal_handle
460
+ rescue RubyLex::TerminateLineInput
461
+ # ignored
462
+ end
463
+ end
464
+
465
+ catch(:IRB_EXIT) do
466
+ irb.eval_input
467
+ end
468
+ ensure
469
+ trap("SIGINT", old_sigint)
470
+ end
471
+
472
+ class << self
473
+ alias :old_CurrentContext :CurrentContext
474
+ remove_method :CurrentContext
475
+ remove_method :parse_opts
476
+ end
477
+
478
+ def IRB.CurrentContext
479
+ if old_CurrentContext.nil? and Breakpoint.use_drb? then
480
+ result = Object.new
481
+ def result.last_value; end
482
+ return result
483
+ else
484
+ old_CurrentContext
485
+ end
486
+ end
487
+ def IRB.parse_opts() end
488
+
489
+ class Context # :nodoc:
490
+ alias :old_evaluate :evaluate
491
+ def evaluate(line, line_no)
492
+ if line.chomp == "exit" then
493
+ exit
494
+ else
495
+ old_evaluate(line, line_no)
496
+ end
497
+ end
498
+ end
499
+
500
+ class WorkSpace # :nodoc:
501
+ alias :old_evaluate :evaluate
502
+
503
+ def evaluate(*args)
504
+ if Breakpoint.use_drb? then
505
+ result = old_evaluate(*args)
506
+ if args[0] != :no_proxy and
507
+ not [true, false, nil].include?(result)
508
+ then
509
+ result.extend(DRbUndumped) rescue nil
510
+ end
511
+ return result
512
+ else
513
+ old_evaluate(*args)
514
+ end
515
+ end
516
+ end
517
+
518
+ module InputCompletor # :nodoc:
519
+ def self.eval(code, context, *more)
520
+ # Big hack, this assumes that InputCompletor
521
+ # will only call eval() when it wants code
522
+ # to be executed in the IRB context.
523
+ IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
524
+ end
525
+ end
526
+ end
527
+
528
+ module DRb # :nodoc:
529
+ class DRbObject # :nodoc:
530
+ undef :inspect if method_defined?(:inspect)
531
+ undef :clone if method_defined?(:clone)
532
+ end
533
+ end
534
+
535
+ # See Breakpoint.breakpoint
536
+ def breakpoint(id = nil, &block)
537
+ Binding.of_caller do |context|
538
+ Breakpoint.breakpoint(id, context, &block)
539
+ end
540
+ end
541
+
542
+ # See Breakpoint.assert
543
+ def assert(&block)
544
+ Binding.of_caller do |context|
545
+ Breakpoint.assert(context, &block)
546
+ end
547
+ end