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,46 @@
1
+ describe Curlybars::SafeBuffer do
2
+ let(:configuration) { double(:configuration) }
3
+
4
+ before do
5
+ allow(Curlybars).to receive(:configuration) { configuration }
6
+ end
7
+
8
+ describe '#is_a?' do
9
+ it "is a String" do
10
+ expect(Curlybars::SafeBuffer.new).to be_a(String)
11
+ end
12
+
13
+ it "is a ActiveSupport::SafeBuffer" do
14
+ expect(Curlybars::SafeBuffer.new).to be_a(ActiveSupport::SafeBuffer)
15
+ end
16
+ end
17
+
18
+ describe "#concat" do
19
+ it "accepts when (buffer length + the existing output lenght) <= output_limit" do
20
+ allow(configuration).to receive(:output_limit) { 10 }
21
+
22
+ buffer = Curlybars::SafeBuffer.new('*' * 5)
23
+
24
+ expect do
25
+ buffer.concat('*' * 5)
26
+ end.not_to raise_error
27
+ end
28
+
29
+ it "raises when (buffer length + the existing output lenght) > output_limit" do
30
+ allow(configuration).to receive(:output_limit) { 10 }
31
+ buffer = Curlybars::SafeBuffer.new('*' * 10)
32
+
33
+ expect do
34
+ buffer.concat('*')
35
+ end.to raise_error(Curlybars::Error::Render)
36
+ end
37
+
38
+ it "raises when buffer length > output_limit" do
39
+ allow(configuration).to receive(:output_limit) { 10 }
40
+
41
+ expect do
42
+ Curlybars::SafeBuffer.new.concat('*' * 11)
43
+ end.to raise_error(Curlybars::Error::Render)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,222 @@
1
+ describe Curlybars::TemplateHandler do
2
+ let :presenter_class do
3
+ Class.new do
4
+ def initialize(context, options = {})
5
+ @context = context
6
+ @cache_key = options.fetch(:cache_key, nil)
7
+ @cache_duration = options.fetch(:cache_duration, nil)
8
+ @cache_options = options.fetch(:cache_options, {})
9
+ end
10
+
11
+ def setup!
12
+ @context.content_for(:foo, "bar")
13
+ end
14
+
15
+ def foo
16
+ "FOO"
17
+ end
18
+
19
+ def bar
20
+ @context.bar
21
+ end
22
+
23
+ def cache_key
24
+ @cache_key
25
+ end
26
+
27
+ def cache_duration
28
+ @cache_duration
29
+ end
30
+
31
+ def cache_options
32
+ @cache_options
33
+ end
34
+
35
+ def allows_method?(method)
36
+ true
37
+ end
38
+ end
39
+ end
40
+
41
+ let :context_class do
42
+ Class.new do
43
+ attr_reader :output_buffer
44
+ attr_reader :local_assigns, :assigns
45
+
46
+ def initialize
47
+ @cache = Hash.new
48
+ @local_assigns = Hash.new
49
+ @assigns = Hash.new
50
+ @clock = 0
51
+ end
52
+
53
+ def reset!
54
+ @output_buffer = ActiveSupport::SafeBuffer.new
55
+ end
56
+
57
+ def advance_clock(duration)
58
+ @clock += duration
59
+ end
60
+
61
+ def content_for(key, value = nil)
62
+ @contents ||= {}
63
+ @contents[key] = value if value.present?
64
+ @contents[key]
65
+ end
66
+
67
+ def cache(key, options = {})
68
+ fragment, expired_at = @cache[key]
69
+
70
+ if fragment.nil? || @clock >= expired_at
71
+ old_buffer = @output_buffer
72
+ @output_buffer = ActiveSupport::SafeBuffer.new
73
+
74
+ yield
75
+
76
+ fragment = @output_buffer.to_s
77
+ duration = options[:expires_in] || Float::INFINITY
78
+
79
+ @cache[key] = [fragment, @clock + duration]
80
+
81
+ @output_buffer = old_buffer
82
+ end
83
+
84
+ safe_concat(fragment)
85
+
86
+ nil
87
+ end
88
+
89
+ def safe_concat(str)
90
+ @output_buffer.safe_concat(str)
91
+ end
92
+ end
93
+ end
94
+
95
+ let(:template) { double("template", virtual_path: "test", identifier: "test.hbs") }
96
+ let(:context) { context_class.new }
97
+
98
+ before do
99
+ stub_const("TestPresenter", presenter_class)
100
+ end
101
+
102
+ it "strips the `# encoding: *` directive away from the template" do
103
+ allow(template).to receive(:source) do
104
+ <<-TEMPLATE.strip_heredoc
105
+ # encoding: utf-8"
106
+ first line
107
+ TEMPLATE
108
+ end
109
+ expect(output).to eq(<<-TEMPLATE.strip_heredoc)
110
+
111
+ first line
112
+ TEMPLATE
113
+ end
114
+
115
+ it "passes in the presenter context to the presenter class" do
116
+ allow(context).to receive(:bar) { "BAR" }
117
+ allow(template).to receive(:source) { "{{bar}}" }
118
+ expect(output).to eq("BAR")
119
+ end
120
+
121
+ it "fails if there's no matching presenter class" do
122
+ allow(template).to receive(:virtual_path) { "missing" }
123
+ allow(template).to receive(:source) { " FOO " }
124
+ expect { output }.to raise_exception(Curlybars::Error::Presenter::NotFound)
125
+ end
126
+
127
+ it "allows calling public methods on the presenter" do
128
+ allow(template).to receive(:source) { "{{foo}}" }
129
+ expect(output).to eq("FOO")
130
+ end
131
+
132
+ it "marks its output as HTML safe" do
133
+ allow(template).to receive(:source) { "{{foo}}" }
134
+ expect(output).to be_html_safe
135
+ end
136
+
137
+ it "calls the #setup! method before rendering the view" do
138
+ allow(template).to receive(:source) { "{{foo}}" }
139
+ output
140
+ expect(context.content_for(:foo)).to eq("bar")
141
+ end
142
+
143
+ context "caching" do
144
+ before do
145
+ allow(template).to receive(:source) { "{{bar}}" }
146
+ allow(context).to receive(:bar) { "BAR" }
147
+ end
148
+
149
+ it "caches the result with the #cache_key from the presenter" do
150
+ context.assigns[:cache_key] = "x"
151
+ expect(output).to eq("BAR")
152
+
153
+ allow(context).to receive(:bar) { "BAZ" }
154
+ expect(output).to eq("BAR")
155
+
156
+ context.assigns[:cache_key] = "y"
157
+ expect(output).to eq("BAZ")
158
+ end
159
+
160
+ it "doesn't cache when the cache key is nil" do
161
+ context.assigns[:cache_key] = nil
162
+ expect(output).to eq("BAR")
163
+
164
+ allow(context).to receive(:bar) { "BAZ" }
165
+ expect(output).to eq("BAZ")
166
+ end
167
+
168
+ it "adds the presenter class' cache key to the instance's cache key" do
169
+ # Make sure caching is enabled
170
+ context.assigns[:cache_key] = "x"
171
+
172
+ allow(presenter_class).to receive(:cache_key) { "foo" }
173
+
174
+ expect(output).to eq("BAR")
175
+
176
+ allow(presenter_class).to receive(:cache_key) { "bar" }
177
+
178
+ allow(context).to receive(:bar) { "FOOBAR" }
179
+ expect(output).to eq("FOOBAR")
180
+ end
181
+
182
+ it "expires the cache keys after #cache_duration" do
183
+ context.assigns[:cache_key] = "x"
184
+ context.assigns[:cache_duration] = 42
185
+
186
+ expect(output).to eq("BAR")
187
+
188
+ allow(context).to receive(:bar) { "FOO" }
189
+
190
+ # Cached fragment has not yet expired.
191
+ context.advance_clock(41)
192
+ expect(output).to eq("BAR")
193
+
194
+ # Now it has! Huzzah!
195
+ context.advance_clock(1)
196
+ expect(output).to eq("FOO")
197
+ end
198
+
199
+ it "passes #cache_options to the cache backend" do
200
+ context.assigns[:cache_key] = "x"
201
+ context.assigns[:cache_options] = { expires_in: 42 }
202
+
203
+ expect(output).to eq("BAR")
204
+
205
+ allow(context).to receive(:bar) { "FOO" }
206
+
207
+ # Cached fragment has not yet expired.
208
+ context.advance_clock(41)
209
+ expect(output).to eq("BAR")
210
+
211
+ # Now it has! Huzzah!
212
+ context.advance_clock(1)
213
+ expect(output).to eq("FOO")
214
+ end
215
+ end
216
+
217
+ def output
218
+ code = Curlybars::TemplateHandler.call(template)
219
+ context.reset!
220
+ context.instance_eval(code)
221
+ end
222
+ end
@@ -0,0 +1,124 @@
1
+ describe "caching" do
2
+ class DummyCache
3
+ attr_reader :reads, :hits
4
+
5
+ def initialize
6
+ @store = {}
7
+ @reads = 0
8
+ @hits = 0
9
+ end
10
+
11
+ def fetch(key)
12
+ @reads += 1
13
+ if @store.key?(key)
14
+ @hits += 1
15
+ @store[key]
16
+ else
17
+ value = yield
18
+ @store[key] = value
19
+ value
20
+ end
21
+ end
22
+ end
23
+
24
+ let(:global_helpers_providers) { [] }
25
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context")) }
26
+ let(:cache) { DummyCache.new }
27
+
28
+ before do
29
+ Curlybars.configure do |config|
30
+ config.cache = cache.method(:fetch)
31
+ end
32
+ end
33
+
34
+ after do
35
+ Curlybars.reset
36
+ end
37
+
38
+ describe "{{#each}}" do
39
+ it "invokes cache if presenter responds to #cache_key" do
40
+ template = Curlybars.compile(<<-HBS)
41
+ {{#each array_of_users}}{{/each}}
42
+ HBS
43
+
44
+ eval(template)
45
+
46
+ expect(cache.reads).to eq(1)
47
+ expect(cache.hits).to eq(0)
48
+ end
49
+
50
+ it "reuses cached values" do
51
+ template = Curlybars.compile(<<-HBS)
52
+ {{#each array_of_users}}
53
+ a
54
+ {{/each}}
55
+
56
+ {{#each array_of_users}}
57
+ a
58
+ {{/each}}
59
+ HBS
60
+
61
+ eval(template)
62
+
63
+ expect(cache.reads).to eq(2)
64
+ expect(cache.hits).to eq(1)
65
+ end
66
+
67
+ it "generates unique cache keys per template" do
68
+ template = Curlybars.compile(<<-HBS)
69
+ {{#each array_of_users}}
70
+ a
71
+ {{/each}}
72
+
73
+ {{#each array_of_users}}
74
+ b
75
+ {{/each}}
76
+ HBS
77
+
78
+ eval(template)
79
+
80
+ expect(cache.reads).to eq(2)
81
+ expect(cache.hits).to eq(0)
82
+ end
83
+
84
+ it "produces correct output from cached presenters" do
85
+ template = Curlybars.compile(<<-HBS)
86
+ {{#each array_of_users}}
87
+ - {{first_name}}
88
+ {{/each}}
89
+ HBS
90
+
91
+ expect(eval(template)).to resemble(<<-HTML)
92
+ - Libo
93
+ HTML
94
+ end
95
+
96
+ it "works for empty templates" do
97
+ template = Curlybars.compile(<<-HBS)
98
+ before
99
+ {{#each array_of_users}}{{/each}}
100
+ {{#each array_of_users}}{{/each}}
101
+ after
102
+ HBS
103
+
104
+ expect(eval(template)).to resemble(<<-HTML)
105
+ before
106
+ after
107
+ HTML
108
+ end
109
+
110
+ it "leaves variables and contexts in correct state after a cache hit" do
111
+ template = Curlybars.compile(<<-HBS)
112
+ {{#each array_of_users}}a{{/each}}
113
+ {{#each array_of_users}}a{{/each}}
114
+ {{context}}
115
+ HBS
116
+
117
+ expect(eval(template)).to resemble(<<-HTML)
118
+ a
119
+ a
120
+ root_context
121
+ HTML
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,60 @@
1
+ describe "{{!-- --}} and {{! }}" do
2
+ let(:post) { double("post") }
3
+ let(:presenter) { IntegrationTest::Presenter.new(double("view_context"), post: post) }
4
+ let(:global_helpers_providers) { [] }
5
+
6
+ it "ignores one line comment" do
7
+ template = Curlybars.compile(<<-HBS)
8
+ before{{! This is a comment }}after
9
+ HBS
10
+
11
+ expect(eval(template)).to resemble(<<-HTML)
12
+ before after
13
+ HTML
14
+ end
15
+
16
+ it "ignores multi line comment" do
17
+ template = Curlybars.compile(<<-HBS)
18
+ before
19
+ {{! 2 lines
20
+ lines }}
21
+ after
22
+ HBS
23
+
24
+ expect(eval(template)).to resemble(<<-HTML)
25
+ before after
26
+ HTML
27
+ end
28
+
29
+ it "ignores multi lines with curly inside comment" do
30
+ template = Curlybars.compile(<<-HBS)
31
+ before
32
+ {{!
33
+ And another one
34
+ in
35
+ 3 lines
36
+ }
37
+ }}
38
+ after
39
+ HBS
40
+
41
+ expect(eval(template)).to resemble(<<-HTML)
42
+ before after
43
+ HTML
44
+ end
45
+
46
+ it "ignores multi line comment with {{!-- --}}" do
47
+ template = Curlybars.compile(<<-HBS)
48
+ before
49
+ {{!--
50
+ And this is the {{ test }} other style
51
+ }}
52
+ --}}
53
+ after
54
+ HBS
55
+
56
+ expect(eval(template)).to resemble(<<-HTML)
57
+ before after
58
+ HTML
59
+ end
60
+ end