liquor 0.1.1 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -9
  5. data/Gemfile +7 -0
  6. data/Guardfile +11 -0
  7. data/MIT-LICENSE +6 -2
  8. data/README.md +4 -122
  9. data/Rakefile +20 -23
  10. data/doc/language-spec.html +768 -0
  11. data/doc/language-spec.md +698 -0
  12. data/lib/liquor.rb +39 -68
  13. data/lib/liquor/ast_tools.rb +28 -0
  14. data/lib/liquor/compiler.rb +110 -0
  15. data/lib/liquor/context.rb +76 -254
  16. data/lib/liquor/diagnostics.rb +151 -0
  17. data/lib/liquor/drop/drop.rb +168 -0
  18. data/lib/liquor/drop/drop_delegation.rb +24 -0
  19. data/lib/liquor/drop/drop_scope.rb +118 -0
  20. data/lib/liquor/drop/dropable.rb +17 -0
  21. data/lib/liquor/emitter.rb +313 -0
  22. data/lib/liquor/extensions/kaminari.rb +14 -0
  23. data/lib/liquor/extensions/pagination.rb +235 -0
  24. data/lib/liquor/extensions/rails.rb +97 -0
  25. data/lib/liquor/extensions/thinking_sphinx.rb +14 -0
  26. data/lib/liquor/extensions/tire.rb +30 -0
  27. data/lib/liquor/external.rb +79 -0
  28. data/lib/liquor/function.rb +94 -0
  29. data/lib/liquor/grammar/lexer.rb +1223 -0
  30. data/lib/liquor/grammar/lexer.rl +297 -0
  31. data/lib/liquor/grammar/parser.racc +288 -0
  32. data/lib/liquor/grammar/parser.rb +885 -0
  33. data/lib/liquor/library.rb +41 -0
  34. data/lib/liquor/manager.rb +146 -0
  35. data/lib/liquor/runtime.rb +167 -0
  36. data/lib/liquor/stdlib/builtin_functions.rb +315 -0
  37. data/lib/liquor/stdlib/builtin_tags.rb +228 -0
  38. data/lib/liquor/stdlib/html_truncater.rb +162 -0
  39. data/lib/liquor/stdlib/partial_tags.rb +76 -0
  40. data/lib/liquor/tag.rb +83 -14
  41. data/lib/liquor/version.rb +1 -1
  42. data/liquor.gemspec +29 -6
  43. data/spec/builtins_spec.rb +264 -0
  44. data/spec/compiler_spec.rb +136 -0
  45. data/spec/context_spec.rb +49 -0
  46. data/spec/drop_delegation_spec.rb +21 -0
  47. data/spec/drop_spec.rb +222 -0
  48. data/spec/errors_spec.rb +40 -0
  49. data/spec/external_spec.rb +207 -0
  50. data/spec/function_spec.rb +80 -0
  51. data/spec/lexer_spec.rb +173 -0
  52. data/spec/library_spec.rb +18 -0
  53. data/spec/manager_spec.rb +84 -0
  54. data/spec/parser_spec.rb +381 -0
  55. data/spec/partials_spec.rb +74 -0
  56. data/spec/runtime_spec.rb +97 -0
  57. data/spec/spec_helper.rb +94 -0
  58. data/spec/tag_spec.rb +7 -0
  59. metadata +216 -173
  60. data/AUTHORS +0 -2
  61. data/CHANGELOG +0 -48
  62. data/Gemfile.lock +0 -91
  63. data/History.txt +0 -44
  64. data/LICENSE +0 -23
  65. data/example/server/example_servlet.rb +0 -37
  66. data/example/server/liquid_servlet.rb +0 -28
  67. data/example/server/liquor_servlet.rb +0 -28
  68. data/example/server/server.rb +0 -12
  69. data/example/server/templates/index.liquid +0 -6
  70. data/example/server/templates/index.liquor +0 -6
  71. data/example/server/templates/products.liquid +0 -45
  72. data/example/server/templates/products.liquor +0 -45
  73. data/init.rb +0 -8
  74. data/lib/extras/liquid_view.rb +0 -51
  75. data/lib/extras/liquor_view.rb +0 -51
  76. data/lib/liquor/block.rb +0 -101
  77. data/lib/liquor/condition.rb +0 -120
  78. data/lib/liquor/document.rb +0 -17
  79. data/lib/liquor/drop.rb +0 -256
  80. data/lib/liquor/errors.rb +0 -11
  81. data/lib/liquor/extensions.rb +0 -72
  82. data/lib/liquor/file_system.rb +0 -62
  83. data/lib/liquor/htmltags.rb +0 -74
  84. data/lib/liquor/module_ex.rb +0 -60
  85. data/lib/liquor/standardfilters.rb +0 -315
  86. data/lib/liquor/strainer.rb +0 -58
  87. data/lib/liquor/tags/assign.rb +0 -33
  88. data/lib/liquor/tags/capture.rb +0 -35
  89. data/lib/liquor/tags/case.rb +0 -83
  90. data/lib/liquor/tags/comment.rb +0 -9
  91. data/lib/liquor/tags/content_for.rb +0 -54
  92. data/lib/liquor/tags/cycle.rb +0 -59
  93. data/lib/liquor/tags/for.rb +0 -136
  94. data/lib/liquor/tags/if.rb +0 -80
  95. data/lib/liquor/tags/ifchanged.rb +0 -20
  96. data/lib/liquor/tags/include.rb +0 -56
  97. data/lib/liquor/tags/unless.rb +0 -33
  98. data/lib/liquor/tags/yield.rb +0 -49
  99. data/lib/liquor/template.rb +0 -181
  100. data/lib/liquor/variable.rb +0 -52
  101. data/performance/shopify.rb +0 -92
  102. data/performance/shopify/comment_form.rb +0 -33
  103. data/performance/shopify/database.rb +0 -45
  104. data/performance/shopify/json_filter.rb +0 -7
  105. data/performance/shopify/liquid.rb +0 -18
  106. data/performance/shopify/liquor.rb +0 -18
  107. data/performance/shopify/money_filter.rb +0 -18
  108. data/performance/shopify/paginate.rb +0 -93
  109. data/performance/shopify/shop_filter.rb +0 -98
  110. data/performance/shopify/tag_filter.rb +0 -25
  111. data/performance/shopify/vision.database.yml +0 -945
  112. data/performance/shopify/weight_filter.rb +0 -11
  113. data/performance/tests/dropify/article.liquid +0 -74
  114. data/performance/tests/dropify/blog.liquid +0 -33
  115. data/performance/tests/dropify/cart.liquid +0 -66
  116. data/performance/tests/dropify/collection.liquid +0 -22
  117. data/performance/tests/dropify/index.liquid +0 -47
  118. data/performance/tests/dropify/page.liquid +0 -8
  119. data/performance/tests/dropify/product.liquid +0 -68
  120. data/performance/tests/dropify/theme.liquid +0 -105
  121. data/performance/tests/ripen/article.liquid +0 -74
  122. data/performance/tests/ripen/blog.liquid +0 -13
  123. data/performance/tests/ripen/cart.liquid +0 -54
  124. data/performance/tests/ripen/collection.liquid +0 -29
  125. data/performance/tests/ripen/index.liquid +0 -32
  126. data/performance/tests/ripen/page.liquid +0 -4
  127. data/performance/tests/ripen/product.liquid +0 -75
  128. data/performance/tests/ripen/theme.liquid +0 -85
  129. data/performance/tests/tribble/404.liquid +0 -56
  130. data/performance/tests/tribble/article.liquid +0 -98
  131. data/performance/tests/tribble/blog.liquid +0 -41
  132. data/performance/tests/tribble/cart.liquid +0 -134
  133. data/performance/tests/tribble/collection.liquid +0 -70
  134. data/performance/tests/tribble/index.liquid +0 -94
  135. data/performance/tests/tribble/page.liquid +0 -56
  136. data/performance/tests/tribble/product.liquid +0 -116
  137. data/performance/tests/tribble/search.liquid +0 -51
  138. data/performance/tests/tribble/theme.liquid +0 -90
  139. data/performance/tests/vogue/article.liquid +0 -66
  140. data/performance/tests/vogue/blog.liquid +0 -32
  141. data/performance/tests/vogue/cart.liquid +0 -58
  142. data/performance/tests/vogue/collection.liquid +0 -19
  143. data/performance/tests/vogue/index.liquid +0 -22
  144. data/performance/tests/vogue/page.liquid +0 -3
  145. data/performance/tests/vogue/product.liquid +0 -62
  146. data/performance/tests/vogue/theme.liquid +0 -122
  147. data/test/assign_test.rb +0 -11
  148. data/test/block_test.rb +0 -58
  149. data/test/capture_test.rb +0 -41
  150. data/test/condition_test.rb +0 -115
  151. data/test/content_for_test.rb +0 -15
  152. data/test/context_test.rb +0 -479
  153. data/test/drop_test.rb +0 -162
  154. data/test/error_handling_test.rb +0 -89
  155. data/test/extra/breakpoint.rb +0 -547
  156. data/test/extra/caller.rb +0 -80
  157. data/test/file_system_test.rb +0 -30
  158. data/test/filter_test.rb +0 -147
  159. data/test/helper.rb +0 -24
  160. data/test/html_tag_test.rb +0 -31
  161. data/test/if_else_test.rb +0 -139
  162. data/test/include_tag_test.rb +0 -129
  163. data/test/module_ex_test.rb +0 -89
  164. data/test/output_test.rb +0 -121
  165. data/test/parsing_quirks_test.rb +0 -54
  166. data/test/regexp_test.rb +0 -45
  167. data/test/security_test.rb +0 -41
  168. data/test/standard_filter_test.rb +0 -170
  169. data/test/standard_tag_test.rb +0 -405
  170. data/test/statements_test.rb +0 -137
  171. data/test/strainer_test.rb +0 -27
  172. data/test/template_test.rb +0 -82
  173. data/test/test_helper.rb +0 -28
  174. data/test/unless_else_test.rb +0 -27
  175. data/test/variable_test.rb +0 -173
  176. data/test/yield_test.rb +0 -24
@@ -1,162 +0,0 @@
1
-
2
- #!/usr/bin/env ruby
3
- require File.dirname(__FILE__) + '/helper'
4
-
5
- class ContextDrop < Liquor::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 < Liquor::Drop
29
-
30
- class TextDrop < Liquor::Drop
31
- def array
32
- ['text1', 'text2']
33
- end
34
-
35
- def text
36
- 'text1'
37
- end
38
- end
39
-
40
- class CatchallDrop < Liquor::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 < Liquor::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 Liquor
80
-
81
- def test_product_drop
82
-
83
- assert_nothing_raised do
84
- tpl = Liquor::Template.parse( ' ' )
85
- tpl.render('product' => ProductDrop.new)
86
- end
87
- end
88
-
89
- def test_text_drop
90
- output = Liquor::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 = Liquor::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 = Liquor::Template.parse( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render('product' => ProductDrop.new)
103
- assert_equal ' text1 text2 ', output
104
- end
105
-
106
- def test_context_drop
107
- output = Liquor::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 = Liquor::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 = Liquor::Template.parse( ' {{ product.callmenot }} ' ).render('product' => ProductDrop.new)
118
- assert_equal ' ', output
119
- end
120
-
121
- def test_scope
122
- assert_equal '1', Liquor::Template.parse( '{{ context.scopes }}' ).render('context' => ContextDrop.new)
123
- assert_equal '2', Liquor::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
124
- assert_equal '3', Liquor::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
125
- end
126
-
127
- def test_scope_though_proc
128
- assert_equal '1', Liquor::Template.parse( '{{ s }}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
129
- assert_equal '2', Liquor::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
130
- assert_equal '3', Liquor::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])
131
- end
132
-
133
- def test_scope_with_assigns
134
- assert_equal 'variable', Liquor::Template.parse( '{% assign a = "variable"%}{{a}}' ).render('context' => ContextDrop.new)
135
- assert_equal 'variable', Liquor::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
136
- assert_equal 'test', Liquor::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render('context' => ContextDrop.new)
137
- assert_equal 'test', Liquor::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', Liquor::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
142
- assert_equal '12', Liquor::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
143
- assert_equal '123', Liquor::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])
144
- end
145
-
146
- def test_access_context_from_drop
147
- assert_equal '123', Liquor::Template.parse( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1,2,3])
148
- end
149
-
150
- def test_enumerable_drop
151
- assert_equal '123', Liquor::Template.parse( '{% for c in collection %}{{c}}{% endfor %}').render('collection' => EnumerableDrop.new)
152
- end
153
-
154
- def test_enumerable_drop_size
155
- assert_equal '3', Liquor::Template.parse( '{{collection.size}}').render('collection' => EnumerableDrop.new)
156
- end
157
-
158
-
159
-
160
- end
161
-
162
-
@@ -1,89 +0,0 @@
1
-
2
- #!/usr/bin/env ruby
3
- require File.dirname(__FILE__) + '/helper'
4
-
5
- class ErrorDrop < Liquor::Drop
6
- def standard_error
7
- raise Liquor::StandardError, 'standard error'
8
- end
9
-
10
- def argument_error
11
- raise Liquor::ArgumentError, 'argument error'
12
- end
13
-
14
- def syntax_error
15
- raise Liquor::SyntaxError, 'syntax error'
16
- end
17
-
18
- end
19
-
20
-
21
- class ErrorHandlingTest < Test::Unit::TestCase
22
- include Liquor
23
-
24
- def test_standard_error
25
- assert_nothing_raised do
26
- template = Liquor::Template.parse( ' {{ errors.standard_error }} ' )
27
- assert_equal ' Liquor 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 = Liquor::Template.parse( ' {{ errors.syntax_error }} ' )
39
- assert_equal ' Liquor 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 = Liquor::Template.parse( ' {{ errors.argument_error }} ' )
53
- assert_equal ' Liquor 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(Liquor::SyntaxError) do
65
-
66
- template = Liquor::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 = Liquor::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
78
- assert_equal ' Liquor error: Unknown operator =! ', template.render
79
-
80
- assert_equal 1, template.errors.size
81
- assert_equal Liquor::ArgumentError, template.errors.first.class
82
-
83
- end
84
-
85
- end
86
-
87
- end
88
-
89
-
@@ -1,547 +0,0 @@
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