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