curlybars 0.9.13

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.
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