liquid 1.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.
- data/CHANGELOG +38 -0
- data/MIT-LICENSE +20 -0
- data/Manifest.txt +60 -0
- data/README +38 -0
- data/Rakefile +24 -0
- data/example/server/example_servlet.rb +37 -0
- data/example/server/liquid_servlet.rb +28 -0
- data/example/server/server.rb +12 -0
- data/example/server/templates/index.liquid +6 -0
- data/example/server/templates/products.liquid +45 -0
- data/init.rb +6 -0
- data/lib/extras/liquid_view.rb +27 -0
- data/lib/liquid.rb +66 -0
- data/lib/liquid/block.rb +101 -0
- data/lib/liquid/condition.rb +91 -0
- data/lib/liquid/context.rb +216 -0
- data/lib/liquid/document.rb +17 -0
- data/lib/liquid/drop.rb +48 -0
- data/lib/liquid/errors.rb +7 -0
- data/lib/liquid/extensions.rb +56 -0
- data/lib/liquid/file_system.rb +62 -0
- data/lib/liquid/htmltags.rb +64 -0
- data/lib/liquid/standardfilters.rb +125 -0
- data/lib/liquid/strainer.rb +43 -0
- data/lib/liquid/tag.rb +25 -0
- data/lib/liquid/tags/assign.rb +22 -0
- data/lib/liquid/tags/capture.rb +22 -0
- data/lib/liquid/tags/case.rb +68 -0
- data/lib/liquid/tags/comment.rb +9 -0
- data/lib/liquid/tags/cycle.rb +46 -0
- data/lib/liquid/tags/for.rb +81 -0
- data/lib/liquid/tags/if.rb +51 -0
- data/lib/liquid/tags/ifchanged.rb +20 -0
- data/lib/liquid/tags/include.rb +56 -0
- data/lib/liquid/tags/unless.rb +29 -0
- data/lib/liquid/template.rb +150 -0
- data/lib/liquid/variable.rb +39 -0
- data/test/block_test.rb +50 -0
- data/test/context_test.rb +340 -0
- data/test/drop_test.rb +139 -0
- data/test/error_handling_test.rb +65 -0
- data/test/extra/breakpoint.rb +547 -0
- data/test/extra/caller.rb +80 -0
- data/test/file_system_test.rb +30 -0
- data/test/filter_test.rb +98 -0
- data/test/helper.rb +20 -0
- data/test/html_tag_test.rb +24 -0
- data/test/if_else_test.rb +95 -0
- data/test/include_tag_test.rb +91 -0
- data/test/output_test.rb +121 -0
- data/test/parsing_quirks_test.rb +14 -0
- data/test/regexp_test.rb +39 -0
- data/test/security_test.rb +41 -0
- data/test/standard_filter_test.rb +101 -0
- data/test/standard_tag_test.rb +336 -0
- data/test/statements_test.rb +137 -0
- data/test/strainer_test.rb +16 -0
- data/test/template_test.rb +26 -0
- data/test/unless_else_test.rb +19 -0
- data/test/variable_test.rb +135 -0
- metadata +114 -0
data/test/drop_test.rb
ADDED
@@ -0,0 +1,139 @@
|
|
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
|
+
|
65
|
+
class DropsTest < Test::Unit::TestCase
|
66
|
+
include Liquid
|
67
|
+
|
68
|
+
def test_product_drop
|
69
|
+
|
70
|
+
assert_nothing_raised do
|
71
|
+
tpl = Liquid::Template.parse( ' ' )
|
72
|
+
tpl.render('product' => ProductDrop.new)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_text_drop
|
77
|
+
output = Liquid::Template.parse( ' {{ product.texts.text }} ' ).render('product' => ProductDrop.new)
|
78
|
+
assert_equal ' text1 ', output
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_text_drop
|
83
|
+
output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render('product' => ProductDrop.new)
|
84
|
+
assert_equal ' method: unknown ', output
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_text_array_drop
|
89
|
+
output = Liquid::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render('product' => ProductDrop.new)
|
90
|
+
assert_equal ' text1 text2 ', output
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_context_drop
|
94
|
+
output = Liquid::Template.parse( ' {{ context.bar }} ' ).render('context' => ContextDrop.new, 'bar' => "carrot")
|
95
|
+
assert_equal ' carrot ', output
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_nested_context_drop
|
99
|
+
output = Liquid::Template.parse( ' {{ product.context.foo }} ' ).render('product' => ProductDrop.new, 'foo' => "monkey")
|
100
|
+
assert_equal ' monkey ', output
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_protected
|
104
|
+
output = Liquid::Template.parse( ' {{ product.callmenot }} ' ).render('product' => ProductDrop.new)
|
105
|
+
assert_equal ' ', output
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_scope
|
109
|
+
assert_equal '1', Liquid::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
|
110
|
+
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
111
|
+
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_scope_though_proc
|
115
|
+
assert_equal '1', Liquid::Template.parse( '{{ s }}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
|
116
|
+
assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
|
117
|
+
assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_scope_with_assigns
|
121
|
+
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render('context' => ContextDrop.new)
|
122
|
+
assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_scope_from_tags
|
126
|
+
assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
127
|
+
assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
128
|
+
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_access_context_from_drop
|
132
|
+
assert_equal '123', Liquid::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1,2,3])
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
#!/usr/bin/env ruby
|
3
|
+
require File.dirname(__FILE__) + '/helper'
|
4
|
+
|
5
|
+
class ErrorDrop < Liquid::Drop
|
6
|
+
def standard_error
|
7
|
+
raise StandardError, 'standard error'
|
8
|
+
end
|
9
|
+
|
10
|
+
def argument_error
|
11
|
+
raise ArgumentError, 'argument error'
|
12
|
+
end
|
13
|
+
|
14
|
+
def syntax_error
|
15
|
+
raise 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
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
@@ -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
|
+
end_line = break_line + context
|
179
|
+
|
180
|
+
result = lines[(start_line - 1) .. (end_line - 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
|