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.
- checksums.yaml +4 -4
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +4 -2
- data/README.md +76 -3
- data/Rakefile +3 -116
- data/circle.yml +1 -1
- data/curly-templates.gemspec +6 -83
- data/lib/curly.rb +1 -1
- data/lib/curly/compiler.rb +2 -12
- data/lib/curly/component_compiler.rb +6 -3
- data/lib/curly/presenter.rb +32 -20
- data/lib/curly/presenter_name_error.rb +16 -0
- data/lib/curly/rspec.rb +16 -0
- data/lib/curly/version.rb +3 -0
- metadata +14 -72
- data/perf/compile_benchmark.rb +0 -71
- data/perf/compile_profile.rb +0 -64
- data/perf/component_benchmark.rb +0 -41
- data/spec/attribute_scanner_spec.rb +0 -44
- data/spec/collection_blocks_spec.rb +0 -78
- data/spec/compiler/collections_spec.rb +0 -230
- data/spec/compiler/context_blocks_spec.rb +0 -106
- data/spec/compiler_spec.rb +0 -192
- data/spec/component_compiler_spec.rb +0 -107
- data/spec/component_scanner_spec.rb +0 -36
- data/spec/components_spec.rb +0 -43
- data/spec/conditional_blocks_spec.rb +0 -40
- data/spec/dummy/.gitignore +0 -1
- data/spec/dummy/app/controllers/application_controller.rb +0 -2
- data/spec/dummy/app/controllers/dashboards_controller.rb +0 -14
- data/spec/dummy/app/helpers/application_helper.rb +0 -5
- data/spec/dummy/app/presenters/dashboards/collection_presenter.rb +0 -7
- data/spec/dummy/app/presenters/dashboards/item_presenter.rb +0 -23
- data/spec/dummy/app/presenters/dashboards/new_presenter.rb +0 -29
- data/spec/dummy/app/presenters/dashboards/partials_presenter.rb +0 -5
- data/spec/dummy/app/presenters/dashboards/show_presenter.rb +0 -12
- data/spec/dummy/app/presenters/layouts/application_presenter.rb +0 -21
- data/spec/dummy/app/views/dashboards/_item.html.curly +0 -1
- data/spec/dummy/app/views/dashboards/collection.html.curly +0 -8
- data/spec/dummy/app/views/dashboards/new.html.curly +0 -7
- data/spec/dummy/app/views/dashboards/partials.html.curly +0 -3
- data/spec/dummy/app/views/dashboards/show.html.curly +0 -3
- data/spec/dummy/app/views/layouts/application.html.curly +0 -11
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/config/application.rb +0 -12
- data/spec/dummy/config/boot.rb +0 -5
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/test.rb +0 -36
- data/spec/dummy/config/routes.rb +0 -6
- data/spec/generators/controller_generator_spec.rb +0 -34
- data/spec/integration/application_layout_spec.rb +0 -22
- data/spec/integration/collection_blocks_spec.rb +0 -37
- data/spec/integration/context_blocks_spec.rb +0 -27
- data/spec/integration/partials_spec.rb +0 -24
- data/spec/matchers/have_structure.rb +0 -29
- data/spec/parser_spec.rb +0 -92
- data/spec/presenter_spec.rb +0 -247
- data/spec/scanner_spec.rb +0 -124
- data/spec/spec_helper.rb +0 -45
- data/spec/syntax_error_spec.rb +0 -12
- data/spec/template_handler_spec.rb +0 -209
data/perf/component_benchmark.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'benchmark/ips'
|
3
|
-
|
4
|
-
ENV["RAILS_ENV"] = "test"
|
5
|
-
|
6
|
-
require_relative '../spec/dummy/config/environment'
|
7
|
-
|
8
|
-
class TestPresenter < Curly::Presenter
|
9
|
-
def hello_with_delegation
|
10
|
-
some_helper
|
11
|
-
end
|
12
|
-
|
13
|
-
def hello_without_delegation
|
14
|
-
not_some_helper
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def not_some_helper
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class TestContext
|
24
|
-
def some_helper
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
context = TestContext.new
|
29
|
-
presenter = TestPresenter.new(context)
|
30
|
-
|
31
|
-
Benchmark.ips do |x|
|
32
|
-
x.report "presenter method that delegates to the view context" do
|
33
|
-
presenter.hello_with_delegation
|
34
|
-
end
|
35
|
-
|
36
|
-
x.report "presenter method that doesn't delegate to the view context" do
|
37
|
-
presenter.hello_without_delegation
|
38
|
-
end
|
39
|
-
|
40
|
-
x.compare!
|
41
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
describe Curly::AttributeScanner do
|
2
|
-
it "scans attributes" do
|
3
|
-
scan("width=10px height=20px").should == {
|
4
|
-
"width" => "10px",
|
5
|
-
"height" => "20px"
|
6
|
-
}
|
7
|
-
end
|
8
|
-
|
9
|
-
it "scans single quoted values" do
|
10
|
-
scan("title='hello world'").should == { "title" => "hello world" }
|
11
|
-
end
|
12
|
-
|
13
|
-
it "scans double quoted values" do
|
14
|
-
scan('title="hello world"').should == { "title" => "hello world" }
|
15
|
-
end
|
16
|
-
|
17
|
-
it "scans mixed quotes" do
|
18
|
-
scan(%[x=y q="foo's bar" v='bim " bum' t="foo ' bar"]).should == {
|
19
|
-
"x" => "y",
|
20
|
-
"q" => "foo's bar",
|
21
|
-
"t" => "foo ' bar",
|
22
|
-
"v" => 'bim " bum'
|
23
|
-
}
|
24
|
-
end
|
25
|
-
|
26
|
-
it "deals with weird whitespace" do
|
27
|
-
scan(" size=big ").should == { "size" => "big" }
|
28
|
-
end
|
29
|
-
|
30
|
-
it "scans empty attribute lists" do
|
31
|
-
scan(nil).should == {}
|
32
|
-
scan("").should == {}
|
33
|
-
scan(" ").should == {}
|
34
|
-
end
|
35
|
-
|
36
|
-
it "fails when an invalid attribute list is passed" do
|
37
|
-
expect { scan("foo") }.to raise_exception(Curly::AttributeError)
|
38
|
-
expect { scan("foo=") }.to raise_exception(Curly::AttributeError)
|
39
|
-
end
|
40
|
-
|
41
|
-
def scan(str)
|
42
|
-
described_class.scan(str)
|
43
|
-
end
|
44
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
describe "Collection block components" do
|
2
|
-
include CompilationSupport
|
3
|
-
|
4
|
-
before do
|
5
|
-
define_presenter "ItemPresenter" do
|
6
|
-
presents :item
|
7
|
-
|
8
|
-
def name
|
9
|
-
@item
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
example "with neither identifier nor attributes" do
|
15
|
-
define_presenter do
|
16
|
-
def items
|
17
|
-
["one", "two", "three"]
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
render("{{*items}}<{{name}}>{{/items}}").should == "<one><two><three>"
|
22
|
-
end
|
23
|
-
|
24
|
-
example "with an identifier" do
|
25
|
-
define_presenter do
|
26
|
-
def items(filter = nil)
|
27
|
-
if filter == "even"
|
28
|
-
["two"]
|
29
|
-
elsif filter == "odd"
|
30
|
-
["one", "three"]
|
31
|
-
else
|
32
|
-
["one", "two", "three"]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
render("{{*items.even}}<{{name}}>{{/items.even}}").should == "<two>"
|
38
|
-
render("{{*items.odd}}<{{name}}>{{/items.odd}}").should == "<one><three>"
|
39
|
-
render("{{*items}}<{{name}}>{{/items}}").should == "<one><two><three>"
|
40
|
-
end
|
41
|
-
|
42
|
-
example "with attributes" do
|
43
|
-
define_presenter do
|
44
|
-
def items(length: "1")
|
45
|
-
["x"] * length.to_i
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
render("{{*items length=3}}<{{name}}>{{/items}}").should == "<x><x><x>"
|
50
|
-
render("{{*items}}<{{name}}>{{/items}}").should == "<x>"
|
51
|
-
end
|
52
|
-
|
53
|
-
example "with nested collection blocks" do
|
54
|
-
define_presenter do
|
55
|
-
def items
|
56
|
-
[{ parts: [1, 2] }, { parts: [3, 4] }]
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
define_presenter "ItemPresenter" do
|
61
|
-
presents :item
|
62
|
-
|
63
|
-
def parts
|
64
|
-
@item[:parts]
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
define_presenter "PartPresenter" do
|
69
|
-
presents :part
|
70
|
-
|
71
|
-
def number
|
72
|
-
@part
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
render("{{*items}}<{{*parts}}[{{number}}]{{/parts}}>{{/items}}").should == "<[1][2]><[3][4]>"
|
77
|
-
end
|
78
|
-
end
|
@@ -1,230 +0,0 @@
|
|
1
|
-
describe Curly::Compiler do
|
2
|
-
include CompilationSupport
|
3
|
-
|
4
|
-
context "normal rendering" do
|
5
|
-
before do
|
6
|
-
define_presenter "ItemPresenter" do
|
7
|
-
presents :item
|
8
|
-
delegate :name, to: :@item
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
it "compiles collection blocks" do
|
13
|
-
define_presenter do
|
14
|
-
presents :items
|
15
|
-
attr_reader :items
|
16
|
-
end
|
17
|
-
|
18
|
-
item1 = double("item1", name: "foo")
|
19
|
-
item2 = double("item2", name: "bar")
|
20
|
-
|
21
|
-
template = "<ul>{{*items}}<li>{{name}}</li>{{/items}}</ul>"
|
22
|
-
expect(render(template, locals: { items: [item1, item2] })).
|
23
|
-
to eql "<ul><li>foo</li><li>bar</li></ul>"
|
24
|
-
end
|
25
|
-
|
26
|
-
it "allows attributes on collection blocks" do
|
27
|
-
define_presenter do
|
28
|
-
presents :items
|
29
|
-
|
30
|
-
def items(status: nil)
|
31
|
-
if status
|
32
|
-
@items.select {|item| item.status == status }
|
33
|
-
else
|
34
|
-
@items
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
item1 = double("item1", name: "foo", status: "active")
|
40
|
-
item2 = double("item2", name: "bar", status: "inactive")
|
41
|
-
|
42
|
-
template = "<ul>{{*items status=active}}<li>{{name}}</li>{{/items}}</ul>"
|
43
|
-
expect(render(template, locals: { items: [item1, item2] })).
|
44
|
-
to eql "<ul><li>foo</li></ul>"
|
45
|
-
end
|
46
|
-
|
47
|
-
it "fails if the component doesn't support enumeration" do
|
48
|
-
template = "<ul>{{*numbers}}<li>{{name}}</li>{{/numbers}}</ul>"
|
49
|
-
expect { render(template) }.to raise_exception(Curly::Error)
|
50
|
-
end
|
51
|
-
|
52
|
-
it "works even if the component method doesn't return an Array" do
|
53
|
-
define_presenter do
|
54
|
-
def companies
|
55
|
-
"Arla"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
define_presenter "CompanyPresenter" do
|
60
|
-
presents :company
|
61
|
-
|
62
|
-
def name
|
63
|
-
@company
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
template = "<ul>{{*companies}}<li>{{name}}</li>{{/companies}}</ul>"
|
68
|
-
expect(render(template)).to eql "<ul><li>Arla</li></ul>"
|
69
|
-
end
|
70
|
-
|
71
|
-
it "passes the index of the current item to the nested presenter" do
|
72
|
-
define_presenter do
|
73
|
-
presents :items
|
74
|
-
attr_reader :items
|
75
|
-
end
|
76
|
-
|
77
|
-
define_presenter "ItemPresenter" do
|
78
|
-
presents :item_counter
|
79
|
-
|
80
|
-
def index
|
81
|
-
@item_counter
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
item1 = double("item1")
|
86
|
-
item2 = double("item2")
|
87
|
-
|
88
|
-
template = "<ul>{{*items}}<li>{{index}}</li>{{/items}}</ul>"
|
89
|
-
expect(render(template, locals: { items: [item1, item2] })).
|
90
|
-
to eql "<ul><li>1</li><li>2</li></ul>"
|
91
|
-
end
|
92
|
-
|
93
|
-
it "restores the previous scope after exiting the collection block" do
|
94
|
-
define_presenter do
|
95
|
-
presents :items
|
96
|
-
attr_reader :items
|
97
|
-
|
98
|
-
def title
|
99
|
-
"Inventory"
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
define_presenter "ItemPresenter" do
|
104
|
-
presents :item
|
105
|
-
delegate :name, :parts, to: :@item
|
106
|
-
end
|
107
|
-
|
108
|
-
define_presenter "PartPresenter" do
|
109
|
-
presents :part
|
110
|
-
delegate :identifier, to: :@part
|
111
|
-
end
|
112
|
-
|
113
|
-
part = double("part", identifier: "X")
|
114
|
-
item = double("item", name: "foo", parts: [part])
|
115
|
-
|
116
|
-
template = "{{*items}}{{*parts}}{{identifier}}{{/parts}}{{name}}{{/items}}{{title}}"
|
117
|
-
expect(render(template, locals: { items: [item] })).
|
118
|
-
to eql "XfooInventory"
|
119
|
-
end
|
120
|
-
|
121
|
-
it "passes the parent presenter's options to the nested presenter" do
|
122
|
-
define_presenter do
|
123
|
-
presents :items, :prefix
|
124
|
-
attr_reader :items
|
125
|
-
end
|
126
|
-
|
127
|
-
define_presenter "ItemPresenter" do
|
128
|
-
presents :item, :prefix
|
129
|
-
delegate :name, to: :@item
|
130
|
-
attr_reader :prefix
|
131
|
-
end
|
132
|
-
|
133
|
-
item1 = double(name: "foo")
|
134
|
-
item2 = double(name: "bar")
|
135
|
-
|
136
|
-
template = "{{*items}}{{prefix}}: {{name}}; {{/items}}"
|
137
|
-
expect(render(template, locals: { prefix: "SKU", items: [item1, item2] })).
|
138
|
-
to eql "SKU: foo; SKU: bar; "
|
139
|
-
end
|
140
|
-
|
141
|
-
it "compiles nested collection blocks" do
|
142
|
-
define_presenter do
|
143
|
-
presents :items
|
144
|
-
attr_reader :items
|
145
|
-
end
|
146
|
-
|
147
|
-
define_presenter "ItemPresenter" do
|
148
|
-
presents :item
|
149
|
-
delegate :name, :parts, to: :@item
|
150
|
-
end
|
151
|
-
|
152
|
-
define_presenter "PartPresenter" do
|
153
|
-
presents :part
|
154
|
-
delegate :identifier, to: :@part
|
155
|
-
end
|
156
|
-
|
157
|
-
item1 = double("item1", name: "item1", parts: [double(identifier: "A"), double(identifier: "B")])
|
158
|
-
item2 = double("item2", name: "item2", parts: [double(identifier: "C"), double(identifier: "D")])
|
159
|
-
|
160
|
-
template = "{{*items}}{{name}}: {{*parts}}{{identifier}}{{/parts}}; {{/items}}"
|
161
|
-
expect(render(template, locals: { items: [item1, item2] })).
|
162
|
-
to eql "item1: AB; item2: CD; "
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
context "re-using assign names" do
|
167
|
-
before do
|
168
|
-
define_presenter do
|
169
|
-
presents :comment
|
170
|
-
|
171
|
-
attr_reader :comment
|
172
|
-
|
173
|
-
def comments
|
174
|
-
["yolo", "xoxo"]
|
175
|
-
end
|
176
|
-
|
177
|
-
def comment(&block)
|
178
|
-
block.call("foo!")
|
179
|
-
end
|
180
|
-
|
181
|
-
def form(&block)
|
182
|
-
block.call
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
define_presenter "CommentPresenter" do
|
187
|
-
presents :comment
|
188
|
-
end
|
189
|
-
|
190
|
-
define_presenter "FormPresenter" do
|
191
|
-
presents :comment
|
192
|
-
attr_reader :comment
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
it "allows re-using assign names in collection blocks" do
|
197
|
-
options = { "comment" => "first post!" }
|
198
|
-
template = "{{*comments}}{{/comments}}{{@form}}{{comment}}{{/form}}"
|
199
|
-
expect(render(template, locals: options)).to eql "first post!"
|
200
|
-
end
|
201
|
-
|
202
|
-
it "allows re-using assign names in context blocks" do
|
203
|
-
options = { "comment" => "first post!" }
|
204
|
-
template = "{{@comment}}{{/comment}}{{@form}}{{comment}}{{/form}}"
|
205
|
-
expect(render(template, locals: options)).to eql "first post!"
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
context "using namespaced names" do
|
210
|
-
before do
|
211
|
-
define_presenter "Layouts::ShowPresenter" do
|
212
|
-
def comments
|
213
|
-
["hello", "world"]
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
define_presenter "Layouts::ShowPresenter::CommentPresenter" do
|
218
|
-
presents :comment
|
219
|
-
|
220
|
-
attr_reader :comment
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
it "renders correctly" do
|
225
|
-
template = "{{*comments}}{{comment}} {{/comments}}"
|
226
|
-
expect(render(template, presenter: "Layouts::ShowPresenter")).
|
227
|
-
to eql "hello world "
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
@@ -1,106 +0,0 @@
|
|
1
|
-
describe Curly::Compiler do
|
2
|
-
include CompilationSupport
|
3
|
-
|
4
|
-
it "compiles context blocks" do
|
5
|
-
define_presenter do
|
6
|
-
def form(&block)
|
7
|
-
"<form>".html_safe + block.call("yo") + "</form>".html_safe
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
define_presenter "FormPresenter" do
|
12
|
-
presents :form
|
13
|
-
|
14
|
-
def text_field(&block)
|
15
|
-
block.call(@form)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
define_presenter "TextFieldPresenter" do
|
20
|
-
presents :text_field
|
21
|
-
|
22
|
-
def field
|
23
|
-
%(<input type="text" value="#{@text_field.upcase}">).html_safe
|
24
|
-
end
|
25
|
-
|
26
|
-
def value?
|
27
|
-
true
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
render('{{@form}}{{@text_field}}{{field}}{{/text_field}}{{/form}}').should == '<form><input type="text" value="YO"></form>'
|
32
|
-
end
|
33
|
-
|
34
|
-
it "compiles using the right presenter" do
|
35
|
-
define_presenter "Layouts::SomePresenter" do
|
36
|
-
|
37
|
-
def contents(&block)
|
38
|
-
block.call("hello, world")
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
define_presenter "Layouts::SomePresenter::ContentsPresenter" do
|
43
|
-
presents :contents
|
44
|
-
|
45
|
-
def contents
|
46
|
-
@contents
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
render("foo: {{@contents}}{{contents}}{{/contents}}", presenter: "Layouts::SomePresenter").should == 'foo: hello, world'
|
51
|
-
end
|
52
|
-
|
53
|
-
it "fails if the component is not a context block" do
|
54
|
-
define_presenter do
|
55
|
-
def form
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
expect {
|
60
|
-
render('{{@form}}{{/form}}')
|
61
|
-
}.to raise_exception(Curly::Error)
|
62
|
-
end
|
63
|
-
|
64
|
-
it "fails if the component doesn't match a presenter class" do
|
65
|
-
define_presenter do
|
66
|
-
def dust(&block)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
expect {
|
71
|
-
render('{{@dust}}{{/dust}}')
|
72
|
-
}.to raise_exception(Curly::Error)
|
73
|
-
end
|
74
|
-
|
75
|
-
it "fails if the component is not a context block" do
|
76
|
-
expect { render('{{@invalid}}yo{{/invalid}}') }.to raise_exception(Curly::Error)
|
77
|
-
end
|
78
|
-
|
79
|
-
it "compiles shorthand context components" do
|
80
|
-
define_presenter do
|
81
|
-
def tree(&block)
|
82
|
-
yield
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
define_presenter "TreePresenter" do
|
87
|
-
def branch(&block)
|
88
|
-
yield
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
define_presenter "BranchPresenter" do
|
93
|
-
def leaf
|
94
|
-
"leaf"
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
render('{{tree:branch:leaf}}').should == "leaf"
|
99
|
-
end
|
100
|
-
|
101
|
-
it "requires shorthand blocks to be closed with the same set of namespaces" do
|
102
|
-
expect do
|
103
|
-
render('{{#tree:branch}}{{/branch}}{{/tree}}')
|
104
|
-
end.to raise_exception(Curly::IncorrectEndingError)
|
105
|
-
end
|
106
|
-
end
|