curly-templates 2.5.0 → 2.6.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +20 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +3 -0
  5. data/.yardopts +3 -0
  6. data/CHANGELOG.md +28 -0
  7. data/Gemfile +4 -2
  8. data/README.md +76 -3
  9. data/Rakefile +3 -116
  10. data/circle.yml +1 -1
  11. data/curly-templates.gemspec +6 -83
  12. data/lib/curly.rb +1 -1
  13. data/lib/curly/compiler.rb +2 -12
  14. data/lib/curly/component_compiler.rb +6 -3
  15. data/lib/curly/presenter.rb +32 -20
  16. data/lib/curly/presenter_name_error.rb +16 -0
  17. data/lib/curly/rspec.rb +16 -0
  18. data/lib/curly/version.rb +3 -0
  19. metadata +14 -72
  20. data/perf/compile_benchmark.rb +0 -71
  21. data/perf/compile_profile.rb +0 -64
  22. data/perf/component_benchmark.rb +0 -41
  23. data/spec/attribute_scanner_spec.rb +0 -44
  24. data/spec/collection_blocks_spec.rb +0 -78
  25. data/spec/compiler/collections_spec.rb +0 -230
  26. data/spec/compiler/context_blocks_spec.rb +0 -106
  27. data/spec/compiler_spec.rb +0 -192
  28. data/spec/component_compiler_spec.rb +0 -107
  29. data/spec/component_scanner_spec.rb +0 -36
  30. data/spec/components_spec.rb +0 -43
  31. data/spec/conditional_blocks_spec.rb +0 -40
  32. data/spec/dummy/.gitignore +0 -1
  33. data/spec/dummy/app/controllers/application_controller.rb +0 -2
  34. data/spec/dummy/app/controllers/dashboards_controller.rb +0 -14
  35. data/spec/dummy/app/helpers/application_helper.rb +0 -5
  36. data/spec/dummy/app/presenters/dashboards/collection_presenter.rb +0 -7
  37. data/spec/dummy/app/presenters/dashboards/item_presenter.rb +0 -23
  38. data/spec/dummy/app/presenters/dashboards/new_presenter.rb +0 -29
  39. data/spec/dummy/app/presenters/dashboards/partials_presenter.rb +0 -5
  40. data/spec/dummy/app/presenters/dashboards/show_presenter.rb +0 -12
  41. data/spec/dummy/app/presenters/layouts/application_presenter.rb +0 -21
  42. data/spec/dummy/app/views/dashboards/_item.html.curly +0 -1
  43. data/spec/dummy/app/views/dashboards/collection.html.curly +0 -8
  44. data/spec/dummy/app/views/dashboards/new.html.curly +0 -7
  45. data/spec/dummy/app/views/dashboards/partials.html.curly +0 -3
  46. data/spec/dummy/app/views/dashboards/show.html.curly +0 -3
  47. data/spec/dummy/app/views/layouts/application.html.curly +0 -11
  48. data/spec/dummy/config.ru +0 -4
  49. data/spec/dummy/config/application.rb +0 -12
  50. data/spec/dummy/config/boot.rb +0 -5
  51. data/spec/dummy/config/environment.rb +0 -5
  52. data/spec/dummy/config/environments/test.rb +0 -36
  53. data/spec/dummy/config/routes.rb +0 -6
  54. data/spec/generators/controller_generator_spec.rb +0 -34
  55. data/spec/integration/application_layout_spec.rb +0 -22
  56. data/spec/integration/collection_blocks_spec.rb +0 -37
  57. data/spec/integration/context_blocks_spec.rb +0 -27
  58. data/spec/integration/partials_spec.rb +0 -24
  59. data/spec/matchers/have_structure.rb +0 -29
  60. data/spec/parser_spec.rb +0 -92
  61. data/spec/presenter_spec.rb +0 -247
  62. data/spec/scanner_spec.rb +0 -124
  63. data/spec/spec_helper.rb +0 -45
  64. data/spec/syntax_error_spec.rb +0 -12
  65. data/spec/template_handler_spec.rb +0 -209
@@ -1,247 +0,0 @@
1
- describe Curly::Presenter do
2
- class CircusPresenter < Curly::Presenter
3
- module MonkeyComponents
4
- def monkey
5
- end
6
- end
7
-
8
- exposes_helper :foo
9
-
10
- include MonkeyComponents
11
-
12
- presents :midget, :clown, default: nil
13
- presents :elephant, default: "Dumbo"
14
- presents :puma, default: -> { 'block' }
15
- presents(:lion) { @elephant.upcase }
16
- presents(:something) { self }
17
-
18
- attr_reader :midget, :clown, :elephant, :puma, :lion, :something
19
- end
20
-
21
- class FrenchCircusPresenter < CircusPresenter
22
- presents :elephant, default: "Babar"
23
- end
24
-
25
- class FancyCircusPresenter < CircusPresenter
26
- presents :champagne
27
- end
28
-
29
- class CircusPresenter::MonkeyPresenter < Curly::Presenter
30
- end
31
-
32
- describe "#initialize" do
33
- let(:context) { double("context") }
34
-
35
- it "sets the presented identifiers as instance variables" do
36
- presenter = CircusPresenter.new(context,
37
- midget: "Meek Harolson",
38
- clown: "Bubbles"
39
- )
40
-
41
- presenter.midget.should == "Meek Harolson"
42
- presenter.clown.should == "Bubbles"
43
- end
44
-
45
- it "raises an exception if a required identifier is not specified" do
46
- expect {
47
- FancyCircusPresenter.new(context, {})
48
- }.to raise_exception(ArgumentError, "required identifier `champagne` missing")
49
- end
50
-
51
- it "allows specifying default values for identifiers" do
52
- # Make sure subclasses can change default values.
53
- french_presenter = FrenchCircusPresenter.new(context)
54
- french_presenter.elephant.should == "Babar"
55
- french_presenter.lion.should == 'BABAR'
56
- french_presenter.puma.should be_a Proc
57
-
58
- # The subclass shouldn't change the superclass' defaults, though.
59
- presenter = CircusPresenter.new(context)
60
- presenter.elephant.should == "Dumbo"
61
- presenter.lion.should == 'DUMBO'
62
- presenter.puma.should be_a Proc
63
- end
64
-
65
- it "doesn't call a block if given as a value for identifiers" do
66
- lion = proc { 'Simba' }
67
- presenter = CircusPresenter.new(context, lion: lion)
68
- presenter.lion.should be lion
69
- end
70
-
71
- it "calls default blocks in the instance of the presenter" do
72
- presenter = CircusPresenter.new(context)
73
- presenter.something.should be presenter
74
- end
75
- end
76
-
77
- describe "#method_missing" do
78
- let(:context) { double("context") }
79
- subject {
80
- CircusPresenter.new(context,
81
- midget: "Meek Harolson",
82
- clown: "Bubbles")
83
- }
84
-
85
- it "delegates calls to the context" do
86
- context.should receive(:undefined).once
87
- subject.undefined
88
- end
89
-
90
- it "allows method calls on context-defined methods" do
91
- context.should receive(:respond_to?).
92
- with(:undefined, false).once.and_return(true)
93
- subject.method(:undefined)
94
- end
95
- end
96
-
97
- describe ".exposes_helper" do
98
- let(:context) { double("context") }
99
- subject {
100
- CircusPresenter.new(context,
101
- midget: "Meek Harolson",
102
- clown: "Bubbles")
103
- }
104
-
105
- it "allows a method as a component" do
106
- CircusPresenter.component_available?(:foo)
107
- end
108
-
109
- it "delegates the call to the context" do
110
- context.should receive(:foo).once
111
- subject.should_not receive(:method_missing)
112
- subject.foo
113
- end
114
-
115
- it "doesn't delegate other calls to the context" do
116
- expect { subject.bar }.to raise_error
117
- end
118
- end
119
-
120
- describe ".presenter_for_path" do
121
- it "returns the presenter class for the given path" do
122
- presenter = double("presenter")
123
- stub_const("Foo::BarPresenter", presenter)
124
-
125
- Curly::Presenter.presenter_for_path("foo/bar").should == presenter
126
- end
127
-
128
- it "returns nil if there is no presenter for the given path" do
129
- Curly::Presenter.presenter_for_path("foo/bar").should be_nil
130
- end
131
-
132
- it "does not swallow exceptions" do
133
- error = NameError.new("omg!", :baz)
134
- String.any_instance.stub(:constantize).and_raise(error)
135
-
136
- expect do
137
- Curly::Presenter.presenter_for_path("foo/bar")
138
- end.to raise_error(NameError)
139
- end
140
- end
141
-
142
- describe ".presenter_for_name" do
143
- it "returns the presenter class for the given name" do
144
- CircusPresenter.presenter_for_name("monkey").should == CircusPresenter::MonkeyPresenter
145
- end
146
-
147
- it "looks in the namespace" do
148
- CircusPresenter.presenter_for_name("french_circus").should == FrenchCircusPresenter
149
- end
150
-
151
- it "returns NameError if the presenter class doesn't exist" do
152
- expect { CircusPresenter.presenter_for_name("clown") }.to raise_exception(NameError)
153
- end
154
- end
155
-
156
- describe ".available_components" do
157
- it "includes the methods on the presenter" do
158
- CircusPresenter.available_components.should include("midget")
159
- end
160
-
161
- it "does not include methods on the Curly::Presenter base class" do
162
- CircusPresenter.available_components.should_not include("cache_key")
163
- end
164
- end
165
-
166
- describe ".component_available?" do
167
- it "returns true if the method is available" do
168
- CircusPresenter.component_available?("midget").should == true
169
- end
170
-
171
- it "returns false if the method is not available" do
172
- CircusPresenter.component_available?("bear").should == false
173
- end
174
- end
175
-
176
- describe ".version" do
177
- it "sets the version of the presenter" do
178
- presenter1 = Class.new(Curly::Presenter) do
179
- version 42
180
- end
181
-
182
- presenter2 = Class.new(Curly::Presenter) do
183
- version 1337
184
- end
185
-
186
- presenter1.version.should == 42
187
- presenter2.version.should == 1337
188
- end
189
-
190
- it "returns 0 if no version has been set" do
191
- presenter = Class.new(Curly::Presenter)
192
- presenter.version.should == 0
193
- end
194
- end
195
-
196
- describe ".cache_key" do
197
- it "includes the presenter's class name and version" do
198
- presenter = Class.new(Curly::Presenter) { version 42 }
199
- stub_const("Foo::BarPresenter", presenter)
200
-
201
- Foo::BarPresenter.cache_key.should == "Foo::BarPresenter/42"
202
- end
203
-
204
- it "includes the cache keys of presenters in the dependency list" do
205
- presenter = Class.new(Curly::Presenter) do
206
- version 42
207
- depends_on 'foo/bum'
208
- end
209
-
210
- dependency = Class.new(Curly::Presenter) do
211
- version 1337
212
- end
213
-
214
- stub_const("Foo::BarPresenter", presenter)
215
- stub_const("Foo::BumPresenter", dependency)
216
-
217
- cache_key = Foo::BarPresenter.cache_key
218
- cache_key.should == "Foo::BarPresenter/42/Foo::BumPresenter/1337"
219
- end
220
-
221
- it "uses the view path of a dependency if there is no presenter for it" do
222
- presenter = Class.new(Curly::Presenter) do
223
- version 42
224
- depends_on 'foo/bum'
225
- end
226
-
227
- stub_const("Foo::BarPresenter", presenter)
228
-
229
- cache_key = Foo::BarPresenter.cache_key
230
- cache_key.should == "Foo::BarPresenter/42/foo/bum"
231
- end
232
- end
233
-
234
- describe ".dependencies" do
235
- it "returns the dependencies defined for the presenter" do
236
- presenter = Class.new(Curly::Presenter) { depends_on 'foo' }
237
- presenter.dependencies.to_a.should == ['foo']
238
- end
239
-
240
- it "includes the dependencies defined for parent classes" do
241
- Curly::Presenter.dependencies
242
- parent = Class.new(Curly::Presenter) { depends_on 'foo' }
243
- presenter = Class.new(parent) { depends_on 'bar' }
244
- presenter.dependencies.to_a.should =~ ['foo', 'bar']
245
- end
246
- end
247
- end
data/spec/scanner_spec.rb DELETED
@@ -1,124 +0,0 @@
1
- describe Curly::Scanner, ".scan" do
2
- it "returns the tokens in the source" do
3
- scan("foo {{bar}} baz").should == [
4
- [:text, "foo "],
5
- [:component, "bar", nil, {}, []],
6
- [:text, " baz"]
7
- ]
8
- end
9
-
10
- it "scans components with identifiers" do
11
- scan("{{foo.bar}}").should == [
12
- [:component, "foo", "bar", {}, []]
13
- ]
14
- end
15
-
16
- it "scans comments in the source" do
17
- scan("foo {{!bar}} baz").should == [
18
- [:text, "foo "],
19
- [:comment, "bar"],
20
- [:text, " baz"]
21
- ]
22
- end
23
-
24
- it "allows newlines in comments" do
25
- scan("{{!\nfoo\n}}").should == [
26
- [:comment, "\nfoo\n"]
27
- ]
28
- end
29
-
30
- it "scans to the end of the source" do
31
- scan("foo\n").should == [
32
- [:text, "foo\n"]
33
- ]
34
- end
35
-
36
- it "allows escaping Curly quotes" do
37
- scan('foo {{{ bar').should == [
38
- [:text, "foo "],
39
- [:text, "{{"],
40
- [:text, " bar"]
41
- ]
42
-
43
- scan('foo }} bar').should == [
44
- [:text, "foo }} bar"]
45
- ]
46
-
47
- scan('foo {{{ lala! }} bar').should == [
48
- [:text, "foo "],
49
- [:text, "{{"],
50
- [:text, " lala! }} bar"]
51
- ]
52
- end
53
-
54
- it "scans context block tags" do
55
- scan('{{@search_form}}{{query_field}}{{/search_form}}').should == [
56
- [:context_block_start, "search_form", nil, {}, []],
57
- [:component, "query_field", nil, {}, []],
58
- [:block_end, "search_form", nil, {}, []]
59
- ]
60
- end
61
-
62
- it "scans conditional block tags" do
63
- scan('foo {{#bar?}} hello {{/bar?}}').should == [
64
- [:text, "foo "],
65
- [:conditional_block_start, "bar?", nil, {}, []],
66
- [:text, " hello "],
67
- [:block_end, "bar?", nil, {}, []]
68
- ]
69
- end
70
-
71
- it "scans conditional block tags with parameters and attributes" do
72
- scan('{{#active.test? name="test"}}yo{{/active.test?}}').should == [
73
- [:conditional_block_start, "active?", "test", { "name" => "test" }, []],
74
- [:text, "yo"],
75
- [:block_end, "active?", "test", {}, []]
76
- ]
77
- end
78
-
79
- it "scans inverse block tags" do
80
- scan('foo {{^bar?}} hello {{/bar?}}').should == [
81
- [:text, "foo "],
82
- [:inverse_conditional_block_start, "bar?", nil, {}, []],
83
- [:text, " hello "],
84
- [:block_end, "bar?", nil, {}, []]
85
- ]
86
- end
87
-
88
- it "scans collection block tags" do
89
- scan('foo {{*bar}} hello {{/bar}}').should == [
90
- [:text, "foo "],
91
- [:collection_block_start, "bar", nil, {}, []],
92
- [:text, " hello "],
93
- [:block_end, "bar", nil, {}, []]
94
- ]
95
- end
96
-
97
- it "treats quotes as text" do
98
- scan('"').should == [
99
- [:text, '"']
100
- ]
101
- end
102
-
103
- it "treats Ruby interpolation as text" do
104
- scan('#{foo}').should == [
105
- [:text, '#{foo}']
106
- ]
107
- end
108
-
109
- it "raises Curly::SyntaxError on unclosed components" do
110
- ["{{", "{{yolo"].each do |template|
111
- expect { scan(template) }.to raise_error(Curly::SyntaxError)
112
- end
113
- end
114
-
115
- it "raises Curly::SyntaxError on unclosed comments" do
116
- ["{{!", "{{! foo bar"].each do |template|
117
- expect { scan(template) }.to raise_error(Curly::SyntaxError)
118
- end
119
- end
120
-
121
- def scan(source)
122
- Curly::Scanner.scan(source)
123
- end
124
- end
data/spec/spec_helper.rb DELETED
@@ -1,45 +0,0 @@
1
- ENV["RAILS_ENV"] = "test"
2
-
3
- require 'dummy/config/environment'
4
- require 'rspec/rails'
5
-
6
- RSpec.configure do |config|
7
- config.infer_spec_type_from_file_location!
8
- end
9
-
10
- module CompilationSupport
11
- def define_presenter(name = "ShowPresenter", &block)
12
- presenter_class = Class.new(Curly::Presenter, &block)
13
- stub_const(name, presenter_class)
14
- presenter_class
15
- end
16
-
17
- def render(source, options = {}, &block)
18
- presenter = options.fetch(:presenter) do
19
- define_presenter("ShowPresenter") unless defined?(ShowPresenter)
20
- "ShowPresenter"
21
- end.constantize
22
-
23
- virtual_path = options.fetch(:virtual_path) do
24
- presenter.name.underscore.gsub(/_presenter\z/, "")
25
- end
26
-
27
- identifier = options.fetch(:identifier) do
28
- defined?(Rails.root) ? "#{Rails.root}/#{virtual_path}.html.curly" : virtual_path
29
- end
30
-
31
- details = { virtual_path: virtual_path }
32
- details.merge! options.fetch(:details, {})
33
-
34
- handler = Curly::TemplateHandler
35
- template = ActionView::Template.new(source, identifier, handler, details)
36
- view = ActionView::Base.new
37
- view.lookup_context.stub(:find_template) { source }
38
-
39
- begin
40
- template.render(view, options.fetch(:locals, {}), &block)
41
- rescue ActionView::Template::Error => e
42
- raise e.original_exception
43
- end
44
- end
45
- end
@@ -1,12 +0,0 @@
1
- describe Curly::SyntaxError, "#message" do
2
- it "includes the context of the error in the message" do
3
- source = "I am a very bad error that has snuck in"
4
- error = Curly::SyntaxError.new(13, source)
5
-
6
- error.message.should == <<-MESSAGE.strip_heredoc
7
- invalid syntax near `a very bad error` on line 1 in template:
8
-
9
- I am a very bad error that has snuck in
10
- MESSAGE
11
- end
12
- end
@@ -1,209 +0,0 @@
1
- describe Curly::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 self.component_available?(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") }
96
- let(:context) { context_class.new }
97
-
98
- before do
99
- stub_const("TestPresenter", presenter_class)
100
- end
101
-
102
- it "passes in the presenter context to the presenter class" do
103
- context.stub(:bar) { "BAR" }
104
- template.stub(:source) { "{{bar}}" }
105
- output.should == "BAR"
106
- end
107
-
108
- it "should fail if there's no matching presenter class" do
109
- template.stub(:virtual_path) { "missing" }
110
- template.stub(:source) { " FOO " }
111
- expect { output }.to raise_exception(Curly::PresenterNotFound)
112
- end
113
-
114
- it "allows calling public methods on the presenter" do
115
- template.stub(:source) { "{{foo}}" }
116
- output.should == "FOO"
117
- end
118
-
119
- it "marks its output as HTML safe" do
120
- template.stub(:source) { "{{foo}}" }
121
- output.should be_html_safe
122
- end
123
-
124
- it "calls the #setup! method before rendering the view" do
125
- template.stub(:source) { "{{foo}}" }
126
- output
127
- context.content_for(:foo).should == "bar"
128
- end
129
-
130
- context "caching" do
131
- before do
132
- template.stub(:source) { "{{bar}}" }
133
- context.stub(:bar) { "BAR" }
134
- end
135
-
136
- it "caches the result with the #cache_key from the presenter" do
137
- context.assigns[:cache_key] = "x"
138
- output.should == "BAR"
139
-
140
- context.stub(:bar) { "BAZ" }
141
- output.should == "BAR"
142
-
143
- context.assigns[:cache_key] = "y"
144
- output.should == "BAZ"
145
- end
146
-
147
- it "doesn't cache when the cache key is nil" do
148
- context.assigns[:cache_key] = nil
149
- output.should == "BAR"
150
-
151
- context.stub(:bar) { "BAZ" }
152
- output.should == "BAZ"
153
- end
154
-
155
- it "adds the presenter class' cache key to the instance's cache key" do
156
- # Make sure caching is enabled
157
- context.assigns[:cache_key] = "x"
158
-
159
- presenter_class.stub(:cache_key) { "foo" }
160
-
161
- output.should == "BAR"
162
-
163
- presenter_class.stub(:cache_key) { "bar" }
164
-
165
- context.stub(:bar) { "FOOBAR" }
166
- output.should == "FOOBAR"
167
- end
168
-
169
- it "expires the cache keys after #cache_duration" do
170
- context.assigns[:cache_key] = "x"
171
- context.assigns[:cache_duration] = 42
172
-
173
- output.should == "BAR"
174
-
175
- context.stub(:bar) { "FOO" }
176
-
177
- # Cached fragment has not yet expired.
178
- context.advance_clock(41)
179
- output.should == "BAR"
180
-
181
- # Now it has! Huzzah!
182
- context.advance_clock(1)
183
- output.should == "FOO"
184
- end
185
-
186
- it "passes #cache_options to the cache backend" do
187
- context.assigns[:cache_key] = "x"
188
- context.assigns[:cache_options] = { expires_in: 42 }
189
-
190
- output.should == "BAR"
191
-
192
- context.stub(:bar) { "FOO" }
193
-
194
- # Cached fragment has not yet expired.
195
- context.advance_clock(41)
196
- output.should == "BAR"
197
-
198
- # Now it has! Huzzah!
199
- context.advance_clock(1)
200
- output.should == "FOO"
201
- end
202
- end
203
-
204
- def output
205
- code = Curly::TemplateHandler.call(template)
206
- context.reset!
207
- context.instance_eval(code)
208
- end
209
- end