curlybars 0.9.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/lib/curlybars.rb +108 -0
  3. data/lib/curlybars/configuration.rb +41 -0
  4. data/lib/curlybars/dependency_tracker.rb +8 -0
  5. data/lib/curlybars/error/base.rb +18 -0
  6. data/lib/curlybars/error/compile.rb +11 -0
  7. data/lib/curlybars/error/lex.rb +22 -0
  8. data/lib/curlybars/error/parse.rb +41 -0
  9. data/lib/curlybars/error/presenter/not_found.rb +23 -0
  10. data/lib/curlybars/error/render.rb +11 -0
  11. data/lib/curlybars/error/validate.rb +18 -0
  12. data/lib/curlybars/lexer.rb +60 -0
  13. data/lib/curlybars/method_whitelist.rb +69 -0
  14. data/lib/curlybars/node/block_helper_else.rb +108 -0
  15. data/lib/curlybars/node/boolean.rb +24 -0
  16. data/lib/curlybars/node/each_else.rb +69 -0
  17. data/lib/curlybars/node/if_else.rb +33 -0
  18. data/lib/curlybars/node/item.rb +31 -0
  19. data/lib/curlybars/node/literal.rb +28 -0
  20. data/lib/curlybars/node/option.rb +25 -0
  21. data/lib/curlybars/node/output.rb +24 -0
  22. data/lib/curlybars/node/partial.rb +24 -0
  23. data/lib/curlybars/node/path.rb +137 -0
  24. data/lib/curlybars/node/root.rb +29 -0
  25. data/lib/curlybars/node/string.rb +24 -0
  26. data/lib/curlybars/node/template.rb +32 -0
  27. data/lib/curlybars/node/text.rb +24 -0
  28. data/lib/curlybars/node/unless_else.rb +33 -0
  29. data/lib/curlybars/node/variable.rb +34 -0
  30. data/lib/curlybars/node/with_else.rb +54 -0
  31. data/lib/curlybars/parser.rb +183 -0
  32. data/lib/curlybars/position.rb +7 -0
  33. data/lib/curlybars/presenter.rb +288 -0
  34. data/lib/curlybars/processor/tilde.rb +31 -0
  35. data/lib/curlybars/processor/token_factory.rb +9 -0
  36. data/lib/curlybars/railtie.rb +18 -0
  37. data/lib/curlybars/rendering_support.rb +222 -0
  38. data/lib/curlybars/safe_buffer.rb +11 -0
  39. data/lib/curlybars/template_handler.rb +93 -0
  40. data/lib/curlybars/version.rb +3 -0
  41. data/spec/acceptance/application_layout_spec.rb +60 -0
  42. data/spec/acceptance/collection_blocks_spec.rb +28 -0
  43. data/spec/acceptance/global_helper_spec.rb +25 -0
  44. data/spec/curlybars/configuration_spec.rb +57 -0
  45. data/spec/curlybars/error/base_spec.rb +41 -0
  46. data/spec/curlybars/error/compile_spec.rb +19 -0
  47. data/spec/curlybars/error/lex_spec.rb +25 -0
  48. data/spec/curlybars/error/parse_spec.rb +74 -0
  49. data/spec/curlybars/error/render_spec.rb +19 -0
  50. data/spec/curlybars/error/validate_spec.rb +19 -0
  51. data/spec/curlybars/lexer_spec.rb +466 -0
  52. data/spec/curlybars/method_whitelist_spec.rb +168 -0
  53. data/spec/curlybars/processor/tilde_spec.rb +60 -0
  54. data/spec/curlybars/rendering_support_spec.rb +426 -0
  55. data/spec/curlybars/safe_buffer_spec.rb +46 -0
  56. data/spec/curlybars/template_handler_spec.rb +222 -0
  57. data/spec/integration/cache_spec.rb +124 -0
  58. data/spec/integration/comment_spec.rb +60 -0
  59. data/spec/integration/exception_spec.rb +31 -0
  60. data/spec/integration/node/block_helper_else_spec.rb +422 -0
  61. data/spec/integration/node/each_else_spec.rb +204 -0
  62. data/spec/integration/node/each_spec.rb +291 -0
  63. data/spec/integration/node/escape_spec.rb +27 -0
  64. data/spec/integration/node/helper_spec.rb +176 -0
  65. data/spec/integration/node/if_else_spec.rb +129 -0
  66. data/spec/integration/node/if_spec.rb +143 -0
  67. data/spec/integration/node/output_spec.rb +68 -0
  68. data/spec/integration/node/partial_spec.rb +66 -0
  69. data/spec/integration/node/path_spec.rb +286 -0
  70. data/spec/integration/node/root_spec.rb +15 -0
  71. data/spec/integration/node/template_spec.rb +86 -0
  72. data/spec/integration/node/unless_else_spec.rb +129 -0
  73. data/spec/integration/node/unless_spec.rb +130 -0
  74. data/spec/integration/node/with_spec.rb +116 -0
  75. data/spec/integration/processor/tilde_spec.rb +38 -0
  76. data/spec/integration/processors_spec.rb +30 -0
  77. metadata +358 -0
@@ -0,0 +1,68 @@
1
+ describe '{{value}}' do
2
+ let(:global_helpers_providers) { [] }
3
+
4
+ describe "#compile" do
5
+ let(:post) { double("post") }
6
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context"), post: post) }
7
+
8
+ it "prints out a string" do
9
+ template = Curlybars.compile(<<-HBS)
10
+ {{'hello world!'}}
11
+ HBS
12
+
13
+ expect(eval(template)).to resemble('hello world!')
14
+ end
15
+
16
+ it "prints out a boolean" do
17
+ template = Curlybars.compile(<<-HBS)
18
+ {{true}}
19
+ HBS
20
+
21
+ expect(eval(template)).to resemble('true')
22
+ end
23
+
24
+ it "prints out an integer" do
25
+ template = Curlybars.compile(<<-HBS)
26
+ {{7}}
27
+ HBS
28
+
29
+ expect(eval(template)).to resemble('7')
30
+ end
31
+
32
+ it "prints out a variable" do
33
+ template = Curlybars.compile(<<-HBS)
34
+ {{#each two_elements}}
35
+ Index: {{@index}}
36
+ First: {{@first}}
37
+ Last: {{@last}}
38
+ {{/each}}
39
+ HBS
40
+
41
+ expect(eval(template)).to resemble(<<-HTML)
42
+ Index: 0
43
+ First: true
44
+ Last: false
45
+
46
+ Index: 1
47
+ First: false
48
+ Last: true
49
+ HTML
50
+ end
51
+ end
52
+
53
+ describe "#validate" do
54
+ let(:presenter_class) { double(:presenter_class) }
55
+
56
+ it "validates the path with errors" do
57
+ dependency_tree = {}
58
+
59
+ source = <<-HBS
60
+ {{unallowed_path}}
61
+ HBS
62
+
63
+ errors = Curlybars.validate(dependency_tree, source)
64
+
65
+ expect(errors).not_to be_empty
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,66 @@
1
+ describe "{{> partial}}" do
2
+ let(:global_helpers_providers) { [] }
3
+
4
+ describe "#compile" do
5
+ let(:post) { double("post") }
6
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context"), post: post) }
7
+
8
+ it "evaluates the methods chain call" do
9
+ template = Curlybars.compile(<<-HBS)
10
+ {{> partial}}
11
+ HBS
12
+
13
+ expect(eval(template)).to resemble(<<-HTML)
14
+ partial
15
+ HTML
16
+ end
17
+
18
+ it "renders nothing when the partial returns `nil`" do
19
+ template = Curlybars.compile(<<-HBS)
20
+ {{> return_nil}}
21
+ HBS
22
+
23
+ expect(eval(template)).to resemble("")
24
+ end
25
+ end
26
+
27
+ describe "#validate" do
28
+ let(:presenter_class) { double(:presenter_class) }
29
+
30
+ it "validates the path with errors" do
31
+ dependency_tree = {}
32
+
33
+ source = <<-HBS
34
+ {{> unallowed_partial}}
35
+ HBS
36
+
37
+ errors = Curlybars.validate(dependency_tree, source)
38
+
39
+ expect(errors).not_to be_empty
40
+ end
41
+
42
+ it "raises when using a helper as a partial" do
43
+ dependency_tree = { helper: nil }
44
+
45
+ source = <<-HBS
46
+ {{> helper}}
47
+ HBS
48
+
49
+ errors = Curlybars.validate(dependency_tree, source)
50
+
51
+ expect(errors).not_to be_empty
52
+ end
53
+
54
+ it "does not raise with a valid partial" do
55
+ dependency_tree = { partial: :partial }
56
+
57
+ source = <<-HBS
58
+ {{> partial}}
59
+ HBS
60
+
61
+ errors = Curlybars.validate(dependency_tree, source)
62
+
63
+ expect(errors).to be_empty
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,286 @@
1
+ describe "{{path}}" do
2
+ let(:global_helpers_providers) { [] }
3
+
4
+ describe "#compile" do
5
+ let(:post) { double("post") }
6
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context"), post: post) }
7
+
8
+ it "evaluates the methods chain call" do
9
+ template = Curlybars.compile(<<-HBS)
10
+ {{user.avatar.url}}
11
+ HBS
12
+
13
+ expect(eval(template)).to resemble(<<-HTML)
14
+ http://example.com/foo.png
15
+ HTML
16
+ end
17
+
18
+ it "is tolerant to paths that return `nil`" do
19
+ template = Curlybars.compile(<<-HBS)
20
+ {{return_nil.property}}
21
+ HBS
22
+
23
+ expect(eval(template)).to resemble("")
24
+ end
25
+
26
+ it "{{../path}} is evaluated on the second to last context in the stack" do
27
+ template = Curlybars.compile(<<-HBS)
28
+ {{#with user.avatar}}
29
+ {{../context}}
30
+ {{/with}}
31
+ HBS
32
+
33
+ expect(eval(template)).to resemble(<<-HTML)
34
+ root_context
35
+ HTML
36
+ end
37
+
38
+ it "{{-a-path-}} is a valid path" do
39
+ template = Curlybars.compile(<<-HBS)
40
+ {{-a-path-}}
41
+ HBS
42
+
43
+ expect(eval(template)).to resemble(<<-HTML)
44
+ a path, whose name contains underscores
45
+ HTML
46
+ end
47
+
48
+ it "{{../../path}} is evaluated on the third to last context in the stack" do
49
+ template = Curlybars.compile(<<-HBS)
50
+ {{#with user}}
51
+ {{#with avatar}}
52
+ {{../../context}}
53
+ {{/with}}
54
+ {{/with}}
55
+ HBS
56
+
57
+ expect(eval(template)).to resemble(<<-HTML)
58
+ root_context
59
+ HTML
60
+ end
61
+
62
+ it "{{../path}} uses the right context, even when using the same name" do
63
+ template = Curlybars.compile(<<-HBS)
64
+ {{#with user}}
65
+ {{#with avatar}}
66
+ {{../context}}
67
+ {{/with}}
68
+ {{../context}}
69
+ {{/with}}
70
+ HBS
71
+
72
+ expect(eval(template)).to resemble(<<-HTML)
73
+ user_context
74
+ root_context
75
+ HTML
76
+ end
77
+
78
+ it "a path with more `../` than the stack size will resolve to empty string" do
79
+ template = Curlybars.compile(<<-HBS)
80
+ {{context}}
81
+ {{../context}}
82
+ {{../../context}}
83
+ HBS
84
+
85
+ expect(eval(template)).to resemble(<<-HTML)
86
+ root_context
87
+ HTML
88
+ end
89
+
90
+ it "understands `this` as the current presenter" do
91
+ template = Curlybars.compile(<<-HBS)
92
+ {{user.avatar.url}}
93
+ {{#with this}}
94
+ {{user.avatar.url}}
95
+ {{/with}}
96
+ HBS
97
+
98
+ expect(eval(template)).to resemble(<<-HTML)
99
+ http://example.com/foo.png
100
+ http://example.com/foo.png
101
+ HTML
102
+ end
103
+
104
+ it "understands `length` as allowed method on a collection" do
105
+ template = Curlybars.compile(<<-HBS)
106
+ {{array_of_users.length}}
107
+ HBS
108
+
109
+ expect(eval(template)).to resemble(<<-HTML)
110
+ 1
111
+ HTML
112
+ end
113
+
114
+ it "raises when trying to call methods not implemented on context" do
115
+ template = Curlybars.compile(<<-HBS)
116
+ {{not_in_whitelist}}
117
+ HBS
118
+
119
+ expect do
120
+ eval(eval(template))
121
+ end.to raise_error(Curlybars::Error::Render)
122
+ end
123
+ end
124
+
125
+ describe "#validate" do
126
+ let(:presenter_class) { double(:presenter_class) }
127
+
128
+ it "without errors" do
129
+ dependency_tree = { sub_context: {}, outer_field: nil }
130
+
131
+ source = <<-HBS
132
+ {{#with sub_context}}
133
+ {{../outer_field}}
134
+ {{/with}}
135
+ HBS
136
+
137
+ errors = Curlybars.validate(dependency_tree, source)
138
+
139
+ expect(errors).to be_empty
140
+ end
141
+
142
+ it "without errors when it goes out of context" do
143
+ dependency_tree = {}
144
+
145
+ source = <<-HBS
146
+ {{../outer_field}}
147
+ HBS
148
+
149
+ errors = Curlybars.validate(dependency_tree, source)
150
+
151
+ expect(errors).to be_empty
152
+ end
153
+
154
+ it "without errors using `this`" do
155
+ dependency_tree = { field: nil }
156
+
157
+ source = <<-HBS
158
+ {{#with this}}
159
+ {{field}}
160
+ {{/with}}
161
+ HBS
162
+
163
+ errors = Curlybars.validate(dependency_tree, source)
164
+
165
+ expect(errors).to be_empty
166
+ end
167
+
168
+ it "without errors using `length` on a collection of presenters" do
169
+ dependency_tree = { collection_of_presenters: [{}] }
170
+
171
+ source = <<-HBS
172
+ {{collection_of_presenters.length}}
173
+ HBS
174
+
175
+ errors = Curlybars.validate(dependency_tree, source)
176
+
177
+ expect(errors).to be_empty
178
+ end
179
+
180
+ it "with errors using `length` on anything else than a collection" do
181
+ dependency_tree = { presenter: {} }
182
+
183
+ source = <<-HBS
184
+ {{presenter.length}}
185
+ HBS
186
+
187
+ errors = Curlybars.validate(dependency_tree, source)
188
+
189
+ expect(errors).not_to be_empty
190
+ end
191
+
192
+ it "with errors when adding another step after `length`" do
193
+ dependency_tree = { presenter: {} }
194
+
195
+ source = <<-HBS
196
+ {{presenter.length.anything}}
197
+ HBS
198
+
199
+ errors = Curlybars.validate(dependency_tree, source)
200
+
201
+ expect(errors).not_to be_empty
202
+ end
203
+
204
+ it "to refer an outer scope using `this`" do
205
+ dependency_tree = { field: nil, sub_presenter: {} }
206
+
207
+ source = <<-HBS
208
+ {{#with sub_presenter}}
209
+ {{#with ../this}}
210
+ {{field}}
211
+ {{/with}}
212
+ {{/with}}
213
+ HBS
214
+
215
+ errors = Curlybars.validate(dependency_tree, source)
216
+
217
+ expect(errors).to be_empty
218
+ end
219
+
220
+ describe "with errors" do
221
+ it "raises with errors" do
222
+ dependency_tree = {}
223
+ errors = Curlybars.validate(dependency_tree, "{{unallowed_path}}")
224
+
225
+ expect(errors).not_to be_empty
226
+ end
227
+
228
+ it "raises with metadata" do
229
+ dependency_tree = {}
230
+ errors = Curlybars.validate(dependency_tree, "{{unallowed_path}}")
231
+
232
+ expect(errors.first.metadata).to eq(path: "unallowed_path", step: :unallowed_path)
233
+ end
234
+
235
+ it "raises with length 0 when the error has no length" do
236
+ dependency_tree = {}
237
+ errors = Curlybars.validate(dependency_tree, "{{ok}")
238
+
239
+ expect(errors.first.position).to eq(Curlybars::Position.new(nil, 1, 4, 0))
240
+ end
241
+
242
+ describe "raises correct error message" do
243
+ let(:dependency_tree) { { ok: nil } }
244
+
245
+ it "when path contains several parts" do
246
+ source = "{{ok.unallowed.ok}}"
247
+ errors = Curlybars.validate(dependency_tree, source)
248
+
249
+ expect(errors.first.message).to eq("not possible to access `unallowed` in `ok.unallowed.ok`")
250
+ end
251
+
252
+ it "when path contains one part" do
253
+ source = "{{unallowed}}"
254
+ errors = Curlybars.validate(dependency_tree, source)
255
+
256
+ expect(errors.first.message).to eq("'unallowed' does not exist")
257
+ end
258
+ end
259
+
260
+ describe "raises exact location of unallowed steps" do
261
+ let(:dependency_tree) { { ok: { allowed: { ok: nil } } } }
262
+
263
+ it "when in a compacted form" do
264
+ source = "{{ok.unallowed.ok}}"
265
+ errors = Curlybars.validate(dependency_tree, source)
266
+
267
+ expect(errors.first.position).to eq(Curlybars::Position.new(nil, 1, 5, 12))
268
+ end
269
+
270
+ it "with `this` keyword" do
271
+ source = "{{ok.this.unallowed.ok}}"
272
+ errors = Curlybars.validate(dependency_tree, source)
273
+
274
+ expect(errors.first.position).to eq(Curlybars::Position.new(nil, 1, 10, 12))
275
+ end
276
+
277
+ it "with some spacing" do
278
+ source = " {{ ok.unallowed.ok }} "
279
+ errors = Curlybars.validate(dependency_tree, source)
280
+
281
+ expect(errors.first.position).to eq(Curlybars::Position.new(nil, 1, 9, 12))
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,15 @@
1
+ describe "root" do
2
+ describe "#validate" do
3
+ let(:presenter_class) { double(:presenter_class) }
4
+
5
+ it "without errors if template is empty" do
6
+ dependency_tree = {}
7
+
8
+ source = ""
9
+
10
+ errors = Curlybars.validate(dependency_tree, source)
11
+
12
+ expect(errors).to be_empty
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,86 @@
1
+ describe "template" do
2
+ let(:global_helpers_providers) { [] }
3
+
4
+ describe "#compile" do
5
+ let(:post) { double("post") }
6
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context"), post: post) }
7
+
8
+ it "raises an exception when contexts stack is too deep (>= 10)" do
9
+ template = Curlybars.compile(hbs_with_depth(10))
10
+
11
+ expect do
12
+ eval(template)
13
+ end.to raise_error(Curlybars::Error::Render)
14
+ end
15
+
16
+ it "raises an exception when contexts stack is not too deep (< 10)" do
17
+ template = Curlybars.compile(hbs_with_depth(9))
18
+
19
+ expect do
20
+ eval(template)
21
+ end.not_to raise_error
22
+ end
23
+
24
+ it "can be empty" do
25
+ template = Curlybars.compile('')
26
+
27
+ expect(eval(template)).to resemble("")
28
+ end
29
+
30
+ it "can contain a single curly" do
31
+ template = Curlybars.compile('{')
32
+
33
+ expect(eval(template)).to resemble(<<-HTML)
34
+ {
35
+ HTML
36
+ end
37
+
38
+ it "can contain a single backslash" do
39
+ template = Curlybars.compile('\\')
40
+
41
+ expect(eval(template)).to resemble(<<-HTML)
42
+ \\
43
+ HTML
44
+ end
45
+
46
+ private
47
+
48
+ def hbs_with_depth(depth)
49
+ hbs = "%s"
50
+ depth.times { hbs %= "{{#with this}}%s{{/with}}" }
51
+ hbs %= ''
52
+ end
53
+ end
54
+
55
+ describe "#validate" do
56
+ let(:presenter_class) { double(:presenter_class) }
57
+
58
+ it "without errors" do
59
+ dependency_tree = { presenter: { field: nil } }
60
+
61
+ source = <<-HBS
62
+ {{#with presenter}}
63
+ {{field}}
64
+ {{/with}}
65
+ HBS
66
+
67
+ errors = Curlybars.validate(dependency_tree, source)
68
+
69
+ expect(errors).to be_empty
70
+ end
71
+
72
+ it "with errors" do
73
+ dependency_tree = { presenter: { field: nil } }
74
+
75
+ source = <<-HBS
76
+ {{#with presenter}}
77
+ {{unallowed}}
78
+ {{/with}}
79
+ HBS
80
+
81
+ errors = Curlybars.validate(dependency_tree, source)
82
+
83
+ expect(errors).not_to be_empty
84
+ end
85
+ end
86
+ end