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
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe Liquor::Context do
4
+ before do
5
+ @compiler = Liquor::Compiler.new
6
+ @context = Liquor::Context.new(@compiler, ['ext', 'ext2'])
7
+ end
8
+
9
+ it "declares variables" do
10
+ @context.type('a').should == :free
11
+ @context.declare 'a'
12
+ @context.type('a').should == :variable
13
+ end
14
+
15
+ it "verifies variable names" do
16
+ expect { @context.declare 'null' }.to raise_error(Liquor::NameError)
17
+ expect { @context.declare 'a' }.not_to raise_error
18
+ expect { @context.declare 'a' }.not_to raise_error
19
+ expect { @context.declare 'ext' }.not_to raise_error
20
+ end
21
+
22
+ it "mangles names" do
23
+ @context.declare '_env'
24
+ @context.access('_env').should_not be_nil
25
+ @context.access('_env').should_not == '_env'
26
+ end
27
+
28
+ it "supports nesting" do
29
+ @context.nest do
30
+ @context.declare 'test'
31
+ end
32
+ expect { @context.access 'test' }.to raise_error(Liquor::NameError)
33
+ end
34
+
35
+ it "shadows variables" do
36
+ @context.declare 'test'
37
+ var_a = @context.access 'test'
38
+
39
+ var_b = @context.nest do
40
+ @context.declare 'test'
41
+ @context.access 'test'
42
+ end
43
+
44
+ var_c = @context.access 'test'
45
+
46
+ var_a.should_not == var_b
47
+ var_a.should == var_c
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ require 'liquor/drop/drop'
2
+
3
+ describe Liquor::DropDelegation do
4
+ before :all do
5
+ class TestModel
6
+ include Liquor::Dropable
7
+ end
8
+
9
+ class TestModelDrop < Liquor::Drop
10
+ end
11
+ end
12
+
13
+ after :all do
14
+ Object.send :remove_const, :TestModel
15
+ Object.send :remove_const, :TestModelDrop
16
+ end
17
+
18
+ it "should correctly unwrap drop classes" do
19
+ Liquor::DropDelegation.unwrap_drop_class(TestModelDrop).should == TestModel
20
+ end
21
+ end
@@ -0,0 +1,222 @@
1
+ require "spec_helper"
2
+
3
+ require "sqlite3"
4
+ require "liquor/drop/drop"
5
+
6
+ ActiveRecord::Base.establish_connection(
7
+ adapter: 'sqlite3',
8
+ database: ':memory:',
9
+ )
10
+
11
+ ActiveRecord::Schema.define force: true do
12
+ create_table "users", force: true do |t|
13
+ t.string "login"
14
+ t.string "email"
15
+ t.string "occupation"
16
+ end
17
+
18
+ create_table "articles", force: true do |t|
19
+ t.integer "user_id"
20
+ t.string "name"
21
+ t.boolean "published"
22
+ end
23
+ end
24
+
25
+ # Models
26
+
27
+ class User < ActiveRecord::Base
28
+ include Liquor::Dropable
29
+
30
+ has_many :articles
31
+
32
+ scope :with_login, ->(login) { where('login = ?', login) }
33
+ scope :with_id, ->(id) { where('id = ?', id) }
34
+ end
35
+
36
+ class Article < ActiveRecord::Base
37
+ include Liquor::Dropable
38
+
39
+ belongs_to :user
40
+
41
+ scope :published, -> { where('published = ?', true) }
42
+ end
43
+
44
+ dhh = User.create login: 'dhh', email: 'dhh@loudthinking.org', occupation: 'developer'
45
+ dhh.articles.create name: 'java sucks', published: false
46
+ dhh.articles.create name: 'rails rules', published: true
47
+
48
+ me = User.create login: 'me', email: 'vassily@poupkin.org', occupation: 'developer'
49
+ me.articles.create name: 'hello world', published: true
50
+
51
+ nate = User.create login: 'xnutsive', email: 'nat@evl.ms', occupation: 'manager'
52
+
53
+ # Drops
54
+
55
+ class UserDrop < Liquor::Drop
56
+ attributes :id, :login, :email
57
+ scopes :with_login, :with_id
58
+
59
+ has_many :articles, scope: [ :published ]
60
+ end
61
+
62
+ class ArticleDrop < Liquor::Drop
63
+ attributes :name, :published
64
+ scopes :published
65
+
66
+ belongs_to :user
67
+ end
68
+
69
+ describe Liquor::Drop do
70
+ before do
71
+ @me = User.find_by_login 'me'
72
+ @dhh = User.find_by_login 'dhh'
73
+ end
74
+
75
+ it "should export attributes" do
76
+ strukt = Struct.new(:a, :b)
77
+ klass = Class.new(Liquor::Drop) do
78
+ attributes :a, :b
79
+ end
80
+
81
+ datum = strukt.new(1, "hello")
82
+ drop = klass.new(datum)
83
+
84
+ exec('{{ drop.a }} {{ drop.b }}', drop: drop).should == '1 hello'
85
+ expect { exec('{{ drop.c }}') }.to raise_error
86
+ end
87
+
88
+ it "should allow iterating" do
89
+ expect {
90
+ exec(%|{% for user in: users do: %}{{ user.id }}{% end for %}|, users: User.to_drop)
91
+ }.not_to raise_error
92
+ end
93
+
94
+ it "should walk relations and stuff" do
95
+ exec('{{ user.login }}', user: @dhh.to_drop).should == 'dhh'
96
+ exec(%|{% for article in: user.articles do: %}{{ article.name }}, {% end for %}|,
97
+ user: @dhh.to_drop).strip.should == 'rails rules,'
98
+ exec(%|{{ size(articles) }}|, articles: Article.to_drop).should == '3'
99
+ exec(%|{{ size(articles.published) }}|, articles: Article.to_drop).should == '2'
100
+ exec(%|{{ size(users.with_login('dhh')) }}|, users: User.to_drop).should == '1'
101
+ exec(%|{% if article.user == null then: %}ok{% end if %}|, article: Article.new.to_drop).should == 'ok'
102
+ end
103
+
104
+ it "should support generic find_by" do
105
+ exec(%|{{ users.find_by(login: "dhh").email }}|, users: User.to_drop).should == 'dhh@loudthinking.org'
106
+ exec(%|{{ users.find_by(email: "vassily@poupkin.org").login }}|, users: User.to_drop).should == 'me'
107
+ exec(%|{{ users.find_by(id: user).email }}|, users: User.to_drop, user: @dhh.to_drop).should == 'dhh@loudthinking.org'
108
+ end
109
+
110
+ it "should return null from find_by" do
111
+ exec(%|{% if users.find_by(login: "123") == null then: %}ok{% end if %}|, users: User.to_drop).should == 'ok'
112
+ end
113
+
114
+ it "should support generic find_all_by and return a tuple" do
115
+ exec(%|{{ size(users.find_all_by(occupation: "developer")) }}|, users: User.to_drop).should == '2'
116
+ exec(%|{{ size(users.find_all_by(occupation: "manager")) }}|, users: User.to_drop).should == '1'
117
+ end
118
+
119
+ it "should support reversing" do
120
+ ltr = exec(%|{% for user in: users do: %}{{ user.id }}{% end for %}|, users: User.to_drop)
121
+ rtl = exec(%|{% for user in: users.reverse do: %}{{ user.id }}{% end for %}|, users: User.to_drop)
122
+ ltr.should == rtl.reverse
123
+ end
124
+
125
+ it "should support except" do
126
+ res = exec(%|{% for user in: users.except(users.find_by(login: 'xnutsive')) do: %}{{ user.login }},{% end for %}|, users: User.to_drop)
127
+ res.split(',').sort.should == %w(me dhh).sort
128
+ end
129
+
130
+ it "should support find_except_by" do
131
+ res = exec(%|{% for user in: users.find_except_by(login: 'xnutsive') do: %}{{ user.login }},{% end for %}|, users: User.to_drop)
132
+ res.split(',').sort.should == %w(me dhh).sort
133
+ end
134
+
135
+ it "should not fail if the [] argument is out of range" do
136
+ exec(%|{% if users[5] == null then: %}ok{% end if %}|, users: User.to_drop).should == 'ok'
137
+ end
138
+
139
+ it "should provide [] access to the elements, returned by find_all_by function" do
140
+ exec(%|
141
+ {% assign found_users = users.find_all_by(occupation: "developer") %}
142
+ {{ found_users[0].login }}
143
+ |, users: User.to_drop).strip.should == 'dhh'
144
+ end
145
+
146
+ it "should accept scope returned by find_all_by in for statements" do
147
+ exec(%|
148
+ {% for user in: users.find_all_by(occupation: "developer") do: %}
149
+ {{ user.login }}
150
+ {% end for %}
151
+ |, users: User.to_drop).split.should == %w[dhh me]
152
+ end
153
+
154
+ it "should accept scope returned by find_all into empty() function" do
155
+ exec(%|{% if !is_empty(users.find_all_by(occupation: "developer")) then: %}it works{%end if%}|, users: User.to_drop).should == "it works"
156
+ exec(%|{% if is_empty(users.find_all_by(occupation: "idiot")) then: %}it works{% end if%}|, users: User.to_drop).should == "it works"
157
+ end
158
+
159
+ it "should support #pluck" do
160
+ exec(%!{{ users.pluck('login') | join with: ', ' }}!, users: User.to_drop).should == 'dhh, me, xnutsive'
161
+ end
162
+
163
+ it "should export model name from Drop and Drop::Scope" do
164
+ exec(%!{{ users.entity }}!, users: User.to_drop).should == 'User'
165
+ exec(%!{{ users.first.entity }}!, users: User.to_drop).should == 'User'
166
+ end
167
+
168
+ it "should return intact source" do
169
+ @dhh.to_drop.source.should == @dhh
170
+ end
171
+
172
+ it "should support equality" do
173
+ (@dhh.to_drop == @dhh.to_drop).should == true
174
+ (@dhh.to_drop.eql? @dhh.to_drop).should == true
175
+ end
176
+
177
+ it "should support include?" do
178
+ lib = Module.new do
179
+ include Liquor::Library
180
+
181
+ function "check", {
182
+ mandatory_named_args: {
183
+ collection: :any,
184
+ element: :any,
185
+ }
186
+ } do |arg, kw|
187
+ kw[:collection].include? kw[:element]
188
+ end
189
+ end
190
+
191
+ compiler = Liquor::Compiler.new
192
+ lib.export compiler
193
+ code = compiler.compile!(parse(%|
194
+ {% if check(collection: collection element: element) then: %}
195
+ yes
196
+ {% end if %}
197
+ |, compiler), [:collection, :element])
198
+
199
+ code.call(collection: User.to_drop, element: @dhh.to_drop).
200
+ strip.should == 'yes'
201
+ code.call(collection: User.to_drop, element: User.new.to_drop).
202
+ strip.should == ''
203
+ end
204
+
205
+ it "should support uniq'ing" do
206
+ @dhh.to_drop.hash.should == @dhh.to_drop.hash
207
+ [@me, @dhh, @dhh].map(&:to_drop).uniq.size.should == 2
208
+ end
209
+
210
+ it "should unwrap arguments to derivative scopes" do
211
+ Liquor::Drop.unwrap_scope_arguments([ @me.to_drop ]).
212
+ should == [@me.id]
213
+ Liquor::Drop.unwrap_scope_arguments([ [@me.to_drop, @dhh.to_drop] ]).
214
+ should == [ [@me.id, @dhh.id] ]
215
+ Liquor::Drop.unwrap_scope_arguments([ 1, { a: @me.to_drop, b: @dhh.to_drop } ]).
216
+ should == [ 1, { a: @me.id, b: @dhh.id } ]
217
+ exec('{{ scope.with_id(obj).first.login }}', scope: User.to_drop, obj: @me.to_drop).
218
+ should == @me.login
219
+ Liquor::Drop.unwrap_scope_arguments([ User.order(:id).to_drop ]).
220
+ should == [ User.pluck(:id).sort ]
221
+ end
222
+ end
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ describe Liquor::Diagnostic do
4
+ it "is not an error" do
5
+ diag = Liquor::Diagnostic.new("foobar")
6
+ diag.error?.should be_false
7
+ end
8
+
9
+ it "decorates source" do
10
+ source = "line 1\nthis is a diagnostic"
11
+ diag = Liquor::Diagnostic.new("foobar", line: 1, start: 10, end: 19)
12
+ diag.decorate(source).should == [
13
+ "this is a diagnostic",
14
+ " ^^^^^^^^^^"
15
+ ]
16
+ diag.as_json.should == {
17
+ message: 'foobar',
18
+ is_error: false,
19
+ location: {
20
+ line: 1,
21
+ start: 10,
22
+ end: 19
23
+ }
24
+ }
25
+ end
26
+ end
27
+
28
+ describe Liquor::Error do
29
+ it "is an error" do
30
+ diag = Liquor::Error.new("foobar")
31
+ diag.error?.should be_true
32
+ end
33
+ end
34
+
35
+ describe Liquor::Deprecation do
36
+ it "is not an error" do
37
+ diag = Liquor::Deprecation.new("foobar")
38
+ diag.error?.should be_false
39
+ end
40
+ end
@@ -0,0 +1,207 @@
1
+ require "spec_helper"
2
+
3
+ describe Liquor::External do
4
+ it "should export methods" do
5
+ klass = Class.new do
6
+ include Liquor::External
7
+
8
+ def yes(param)
9
+ "result #{param}"
10
+ end
11
+ export :yes
12
+
13
+ def no
14
+ "not accessible"
15
+ end
16
+ end
17
+
18
+ klass.should respond_to(:liquor_exports)
19
+ klass.liquor_exports.should == Set[:yes]
20
+
21
+ obj = klass.new
22
+ obj.liquor_send(:yes, 1).should == "result 1"
23
+ expect { obj.liquor_send(:no) }.to raise_error
24
+ expect { obj.liquor_send("yes", 1) }.not_to raise_error
25
+ end
26
+
27
+ it "should correctly support inheritance" do
28
+ klass = Class.new do
29
+ include Liquor::External
30
+
31
+ def first
32
+ end
33
+ export :first
34
+ end
35
+
36
+ klass2 = Class.new(klass) do
37
+ def second
38
+ end
39
+
40
+ def third
41
+ end
42
+
43
+ export :second, :third
44
+ end
45
+
46
+ klass.liquor_exports.should == Set[:first]
47
+ klass2.liquor_exports.should == Set[:first, :second, :third]
48
+ end
49
+
50
+ it "should be callable" do
51
+ klass = Class.new do
52
+ include Liquor::External
53
+
54
+ def static
55
+ "hello"
56
+ end
57
+
58
+ def dynamic(var)
59
+ "#{var} world"
60
+ end
61
+
62
+ def other
63
+ self
64
+ end
65
+
66
+ def with_hash(param, options={})
67
+ "#{param} #{options.map{ |k,v| "#{k}: #{v}" }.join(" ")}"
68
+ end
69
+
70
+ export :static, :dynamic, :other, :with_hash
71
+ end
72
+
73
+ instance = klass.new
74
+
75
+ exec(%Q|{{ ext.static }}|, ext: instance).should == 'hello'
76
+ exec(%Q|{{ ext.other.static }}|, ext: instance).should == 'hello'
77
+ expect { exec(%Q|{{ ext.dynamic }}|, ext: instance) }.to raise_error
78
+ exec(%Q|{{ ext.dynamic() }}|, ext: instance).should == ' world'
79
+ exec(%Q|{{ ext.dynamic('bye') }}|, ext: instance).should == 'bye world'
80
+ exec(%Q|{{ ext.with_hash("1" two: "3") }}|, ext: instance).should == "1 two: 3"
81
+ end
82
+
83
+ it "should support indexing" do
84
+ klass = Class.new do
85
+ include Liquor::External
86
+
87
+ def [](index)
88
+ "element #{index}"
89
+ end
90
+ export :[]
91
+ end
92
+
93
+ instance = klass.new
94
+
95
+ exec(%Q|{{ ext[10] }}|, ext: instance).should == 'element 10'
96
+ end
97
+
98
+ it "should wrap host errors" do
99
+ klass = Class.new do
100
+ include Liquor::External
101
+
102
+ def fail
103
+ Time.parse("nothing")
104
+ end
105
+ export :fail
106
+ end
107
+
108
+ instance = klass.new
109
+
110
+ expect {
111
+ exec(%Q|{{ ext.fail }}|, ext: instance)
112
+ }.to raise_error(Liquor::HostError)
113
+ end
114
+
115
+ it "should index chained externals" do
116
+ klass = Class.new do
117
+ include Liquor::External
118
+
119
+ def other
120
+ self
121
+ end
122
+
123
+ def [](index)
124
+ index
125
+ end
126
+
127
+ export :other, :[]
128
+ end
129
+
130
+ instance = klass.new
131
+
132
+ exec(%Q|{{ ext.other[5] }}|, ext: instance).should == '5'
133
+ end
134
+
135
+ it "should correctly pass arrays" do
136
+ klass = Class.new do
137
+ include Liquor::External
138
+
139
+ attr_reader :memorized
140
+
141
+ def memorize(arg)
142
+ @memorized = arg
143
+ "d'oh"
144
+ end
145
+
146
+ export :memorize
147
+ end
148
+
149
+ instance = klass.new
150
+
151
+ expect {
152
+ exec(%Q|{{ ext.memorize(["a", "b"]) }}|, ext: instance)
153
+ }.not_to raise_error
154
+
155
+ instance.memorized.should == ["a", "b"]
156
+ end
157
+
158
+ it "concatenates indexable externals with tuples" do
159
+ klass = Class.new do
160
+ include Liquor::External
161
+
162
+ def initialize(array)
163
+ @array = array
164
+ end
165
+
166
+ def [](index); @array[index]; end
167
+ def size; @array.size; end
168
+ def to_a; @array; end
169
+
170
+ export :[], :size
171
+ end
172
+
173
+ exec(%Q|{% for i in: ext + ["b", "c"] do: %}{{ i }} {% end for %}|,
174
+ ext: klass.new(["0", "a"])).strip.should == "0 a b c"
175
+ end
176
+
177
+ it "sets up deprecations" do
178
+ klass = Class.new do
179
+ include Liquor::External
180
+ end
181
+
182
+ expect {
183
+ klass.class_eval { deprecate :foo }
184
+ }.to raise_error(ArgumentError)
185
+
186
+ expect {
187
+ klass.class_eval { deprecate :foo, date: '2012-03-14' }
188
+ }.to raise_error(ArgumentError)
189
+
190
+ expect {
191
+ klass.class_eval { deprecate :foo, message: 'not available' }
192
+ }.to raise_error(ArgumentError)
193
+
194
+ expect {
195
+ klass.class_eval { deprecate :foo, date: '2012-03-14', message: 'not available', foo: 'bar' }
196
+ }.to raise_error(ArgumentError)
197
+
198
+ klass.class_eval {
199
+ deprecate :foo, :bar, date: '2012-03-14', message: 'not available'
200
+ }
201
+
202
+ klass.liquor_deprecations.should == {
203
+ foo: { date: Date.parse('2012-03-14'), message: 'not available' },
204
+ bar: { date: Date.parse('2012-03-14'), message: 'not available' }
205
+ }
206
+ end
207
+ end