curly-templates 1.0.1 → 2.0.0.beta1
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/CHANGELOG.md +13 -4
- data/README.md +179 -8
- data/Rakefile +1 -1
- data/curly-templates.gemspec +10 -3
- data/lib/curly.rb +4 -4
- data/lib/curly/attribute_parser.rb +69 -0
- data/lib/curly/compiler.rb +77 -47
- data/lib/curly/component_compiler.rb +119 -0
- data/lib/curly/component_parser.rb +13 -0
- data/lib/curly/incomplete_block_error.rb +3 -3
- data/lib/curly/incorrect_ending_error.rb +17 -3
- data/lib/curly/invalid_component.rb +13 -0
- data/lib/curly/presenter.rb +31 -11
- data/lib/curly/scanner.rb +24 -11
- data/spec/attribute_parser_spec.rb +46 -0
- data/spec/compiler/collections_spec.rb +153 -0
- data/spec/compiler_spec.rb +18 -34
- data/spec/component_compiler_spec.rb +160 -0
- data/spec/incorrect_ending_error_spec.rb +13 -0
- data/spec/presenter_spec.rb +27 -10
- data/spec/scanner_spec.rb +22 -13
- data/spec/spec_helper.rb +15 -0
- data/spec/template_handler_spec.rb +1 -1
- metadata +16 -5
- data/lib/curly/invalid_reference.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f70027aee738dbd7ac5cc496cce7017943304833
|
4
|
+
data.tar.gz: 68947fc8e15e97945fcba195b0c278e14930d9dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3d6816a0d6fb0e6125cc89845bea00ef363e03012f9bc96e083d0d0951febb032b31472a8bcb23271c16333a470a881e2bf0c4129b8712f06e4f5c24aa86c4b
|
7
|
+
data.tar.gz: 141f19f518dc381ecde8ac6d451bde20551bdcc711c4a9d1a7aa0f981cb100e1e840feb603a01eed956d5e865c3e6c7ef95d736f6302921fb29d3ad858c3150b
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,18 @@
|
|
1
|
-
###
|
1
|
+
### Unreleased
|
2
2
|
|
3
|
-
|
4
|
-
order.
|
3
|
+
### Curly 2.0.0.beta1 (June 27, 2014)
|
5
4
|
|
6
|
-
|
5
|
+
* Add support for collection blocks.
|
6
|
+
|
7
|
+
*Daniel Schierbeck*
|
8
|
+
|
9
|
+
* Add support for keyword parameters to references.
|
10
|
+
|
11
|
+
*Alisson Cavalcante Agiani, Jeremy Rodi, and Daniel Schierbeck*
|
12
|
+
|
13
|
+
* Remove memory leak that could cause unbounded memory growth.
|
14
|
+
|
15
|
+
*Daniel Schierbeck*
|
7
16
|
|
8
17
|
### Curly 1.0.0rc1 (February 18, 2014)
|
9
18
|
|
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
Note: this documentation is for the upcoming Curly v2 – you may be looking for the
|
2
|
+
[documentation for the latest final release](https://github.com/zendesk/curly/tree/1-0-stable).
|
3
|
+
|
4
|
+
|
1
5
|
Curly
|
2
6
|
=======
|
3
7
|
|
@@ -24,8 +28,13 @@ or [Handlebars](http://handlebarsjs.com/), Curly is different in some key ways:
|
|
24
28
|
|
25
29
|
1. [Installing](#installing)
|
26
30
|
2. [How to use Curly](#how-to-use-curly)
|
31
|
+
1. [Identifiers](#identifiers)
|
32
|
+
1. [Attributes](#attributes)
|
27
33
|
1. [Conditional blocks](#conditional-blocks)
|
34
|
+
1. [Collection blocks](#collection-blocks)
|
35
|
+
1. [Setting up state](#setting-up-state)
|
28
36
|
2. [Escaping Curly syntax](#escaping-curly-syntax)
|
37
|
+
2. [Comments](#comments)
|
29
38
|
3. [Presenters](#presenters)
|
30
39
|
1. [Layouts and Content Blocks](#layouts-and-content-blocks)
|
31
40
|
2. [Examples](#examples)
|
@@ -53,7 +62,7 @@ these are placed in `app/presenters/`, so in this case the presenter would
|
|
53
62
|
reside in `app/presenters/posts/comment_presenter.rb`. Note that presenters
|
54
63
|
for partials are not prepended with an underscore.
|
55
64
|
|
56
|
-
Add some HTML to the partial template along with some Curly
|
65
|
+
Add some HTML to the partial template along with some Curly components:
|
57
66
|
|
58
67
|
```html
|
59
68
|
<!-- app/views/posts/_comment.html.curly -->
|
@@ -70,8 +79,8 @@ Add some HTML to the partial template along with some Curly variables:
|
|
70
79
|
</div>
|
71
80
|
```
|
72
81
|
|
73
|
-
The presenter will be responsible for
|
74
|
-
Ruby code to the presenter:
|
82
|
+
The presenter will be responsible for providing the data for the components. Add
|
83
|
+
the necessary Ruby code to the presenter:
|
75
84
|
|
76
85
|
```ruby
|
77
86
|
# app/presenters/posts/comment_presenter.rb
|
@@ -108,6 +117,54 @@ render comment
|
|
108
117
|
render collection: post.comments
|
109
118
|
```
|
110
119
|
|
120
|
+
### Identifiers
|
121
|
+
|
122
|
+
Curly components can specify an _identifier_ using the so-called dot notation: `{{x.y.z}}`.
|
123
|
+
This can be very useful if the data you're accessing is hierarchical in nature. One common
|
124
|
+
example is I18n:
|
125
|
+
|
126
|
+
```html
|
127
|
+
<h1>{{i18n.homepage.header}}</h1>
|
128
|
+
```
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# In the presenter, the identifier is passed as an argument to the method. The
|
132
|
+
# argument will always be a String.
|
133
|
+
def i18n(key)
|
134
|
+
translate(key)
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
The identifier is separated from the component name with a dot. If the presenter method
|
139
|
+
has a default value for the argument, the identifier is optional – otherwise it's mandatory.
|
140
|
+
|
141
|
+
|
142
|
+
### Attributes
|
143
|
+
|
144
|
+
In addition to [an identifier](#identifiers), Curly components can be annotated
|
145
|
+
with *attributes*. These are key-value pairs that affect how a component is rendered.
|
146
|
+
|
147
|
+
The syntax is reminiscent of HTML:
|
148
|
+
|
149
|
+
```html
|
150
|
+
<div>{{sidebar rows=3 width=200px title="I'm the sidebar!"}}</div>
|
151
|
+
```
|
152
|
+
|
153
|
+
The presenter method that implements the component must have a matching keyword argument:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
def sidebar(rows: "1", width: "100px", title:); end
|
157
|
+
```
|
158
|
+
|
159
|
+
All argument values will be strings. A compilation error will be raised if
|
160
|
+
|
161
|
+
- an attribute is used in a component without a matching keyword argument being present
|
162
|
+
in the method definition; or
|
163
|
+
- a required keyword argument in the method definition is not set as an attribute in the
|
164
|
+
component.
|
165
|
+
|
166
|
+
You can define default values using Ruby's own syntax.
|
167
|
+
|
111
168
|
|
112
169
|
### Conditional blocks
|
113
170
|
|
@@ -116,8 +173,8 @@ use _conditional blocks_. The `{{#admin?}}...{{/admin?}}` syntax will only rende
|
|
116
173
|
content of the block if the `admin?` method on the presenter returns true, while the
|
117
174
|
`{{^admin?}}...{{/admin?}}` syntax will only render the content if it returns false.
|
118
175
|
|
119
|
-
Both forms can
|
120
|
-
|
176
|
+
Both forms can have an identifier: `{{#locale.en?}}...{{/locale.en?}}` will only
|
177
|
+
render the block if the `locale?` method on the presenter returns true given the
|
121
178
|
argument `"en"`. Here's how to implement that method in the presenter:
|
122
179
|
|
123
180
|
```ruby
|
@@ -129,6 +186,108 @@ class SomePresenter < Curly::Presenter
|
|
129
186
|
end
|
130
187
|
```
|
131
188
|
|
189
|
+
Furthermore, attributes can be set on the block. These only need to be specified when
|
190
|
+
opening the block, not when closing it:
|
191
|
+
|
192
|
+
```html
|
193
|
+
{{#square? width=3 height=3}}
|
194
|
+
<p>It's square!</p>
|
195
|
+
{{/square?}}
|
196
|
+
```
|
197
|
+
|
198
|
+
Attributes work the same way as they do for normal components.
|
199
|
+
|
200
|
+
|
201
|
+
### Collection blocks
|
202
|
+
|
203
|
+
Sometimes you want to render one or more items within the current template, and splitting
|
204
|
+
out a separate template and rendering that in the presenter is too much overhead. You can
|
205
|
+
instead define the template that should be used to render the items inline in the current
|
206
|
+
template using the _collection block syntax_.
|
207
|
+
|
208
|
+
Collection blocks are opened using an asterisk:
|
209
|
+
|
210
|
+
```html
|
211
|
+
{{*comments}}
|
212
|
+
<li>{{body}} ({{author_name}})</li>
|
213
|
+
{{/comments}}
|
214
|
+
```
|
215
|
+
|
216
|
+
The presenter will need to expose the method `#comments`, which should return a collection
|
217
|
+
of objects:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
class Posts::ShowPresenter < Curly::Presenter
|
221
|
+
presents :post
|
222
|
+
|
223
|
+
def comments
|
224
|
+
@post.comments
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
The template within the collection block will be used to render each item, and it will
|
230
|
+
be backed by a presenter named after the component – in this case, `comments`. The name
|
231
|
+
will be singularized and Curly will try to find the presenter class in the following
|
232
|
+
order:
|
233
|
+
|
234
|
+
* `Posts::ShowPresenter::CommentPresenter`
|
235
|
+
* `Posts::CommentPresenter`
|
236
|
+
* `CommentPresenter`
|
237
|
+
|
238
|
+
This allows you some flexibility with regards to how you want to organize these nested
|
239
|
+
templates and presenters.
|
240
|
+
|
241
|
+
Note that the nested template will *only* have access to the methods on the nested
|
242
|
+
presenter, but all variables passed to the "parent" presenter will be forwarded to
|
243
|
+
the nested presenter. In addition, the current item in the collection will be
|
244
|
+
passed, as well as that item's index in the collection:
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
class Posts::CommentPresenter < Curly::Presenter
|
248
|
+
presents :post, :comment, :comment_counter
|
249
|
+
|
250
|
+
def number
|
251
|
+
# `comment_counter` is automatically set to the item's index in the collection,
|
252
|
+
# starting with 1.
|
253
|
+
@comment_counter
|
254
|
+
end
|
255
|
+
|
256
|
+
def body
|
257
|
+
@comment.body
|
258
|
+
end
|
259
|
+
|
260
|
+
def author_name
|
261
|
+
@comment.author.name
|
262
|
+
end
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
Collection blocks are an alternative to splitting out a separate template and rendering
|
267
|
+
that from the presenter – which solution is best depends on your use case.
|
268
|
+
|
269
|
+
|
270
|
+
### Setting up state
|
271
|
+
|
272
|
+
Although most code in Curly presenters should be free of side effects, sometimes side
|
273
|
+
effects are required. One common example is defining content for a `content_for` block.
|
274
|
+
|
275
|
+
If a Curly presenter class defines a `setup!` method, it will be called before the view
|
276
|
+
is rendered:
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
class PostPresenter < Curly::Presenter
|
280
|
+
presents :post
|
281
|
+
|
282
|
+
def setup!
|
283
|
+
content_for :title, post.title
|
284
|
+
|
285
|
+
content_for :sidebar do
|
286
|
+
render 'post_sidebar', post: post
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
```
|
132
291
|
|
133
292
|
### Escaping Curly syntax
|
134
293
|
|
@@ -141,6 +300,16 @@ This is {{{escaped}}.
|
|
141
300
|
You don't need to escape the closing `}}`.
|
142
301
|
|
143
302
|
|
303
|
+
### Comments
|
304
|
+
|
305
|
+
If you want to add comments to your Curly templates that are not visible in the rendered HTML,
|
306
|
+
use the following syntax:
|
307
|
+
|
308
|
+
```html
|
309
|
+
{{! This is some interesting stuff }}
|
310
|
+
```
|
311
|
+
|
312
|
+
|
144
313
|
Presenters
|
145
314
|
----------
|
146
315
|
|
@@ -151,7 +320,7 @@ rendering `posts/show`, the `Posts::ShowPresenter` class will be used. Note that
|
|
151
320
|
is only used to render a view if a template can be found – in this case, at
|
152
321
|
`app/views/posts/show.html.curly`.
|
153
322
|
|
154
|
-
Presenters can declare a list of accepted
|
323
|
+
Presenters can declare a list of accepted variables using the `presents` method:
|
155
324
|
|
156
325
|
```ruby
|
157
326
|
class Posts::ShowPresenter < Curly::Presenter
|
@@ -159,7 +328,7 @@ class Posts::ShowPresenter < Curly::Presenter
|
|
159
328
|
end
|
160
329
|
```
|
161
330
|
|
162
|
-
A
|
331
|
+
A variable can have a default value:
|
163
332
|
|
164
333
|
```ruby
|
165
334
|
class Posts::ShowPresenter < Curly::Presenter
|
@@ -168,7 +337,8 @@ class Posts::ShowPresenter < Curly::Presenter
|
|
168
337
|
end
|
169
338
|
```
|
170
339
|
|
171
|
-
Any public method defined on the presenter is made available to the template
|
340
|
+
Any public method defined on the presenter is made available to the template as
|
341
|
+
a component:
|
172
342
|
|
173
343
|
```ruby
|
174
344
|
class Posts::ShowPresenter < Curly::Presenter
|
@@ -420,6 +590,7 @@ Thanks to [Zendesk](http://zendesk.com/) for sponsoring the work on Curly.
|
|
420
590
|
- Daniel Schierbeck ([@dasch](https://github.com/dasch))
|
421
591
|
- Benjamin Quorning ([@bquorning](https://github.com/bquorning))
|
422
592
|
- Jeremy Rodi ([@redjazz96](https://github.com/redjazz96))
|
593
|
+
- Alisson Cavalcante Agiani ([@thelinuxlich](https://github.com/thelinuxlich))
|
423
594
|
|
424
595
|
|
425
596
|
Build Status
|
data/Rakefile
CHANGED
@@ -65,7 +65,7 @@ end
|
|
65
65
|
|
66
66
|
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
67
67
|
task :release => :build do
|
68
|
-
unless `git branch` =~ /^\* master$/
|
68
|
+
unless `git branch` =~ /^\* master$/
|
69
69
|
puts "You must be on the master branch to release!"
|
70
70
|
exit!
|
71
71
|
end
|
data/curly-templates.gemspec
CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
|
|
4
4
|
s.rubygems_version = '1.3.5'
|
5
5
|
|
6
6
|
s.name = 'curly-templates'
|
7
|
-
s.version = '
|
8
|
-
s.date = '2014-06-
|
7
|
+
s.version = '2.0.0.beta1'
|
8
|
+
s.date = '2014-06-27'
|
9
9
|
|
10
10
|
s.summary = "Free your views!"
|
11
11
|
s.description = "A view layer for your Rails apps that separates structure and logic."
|
@@ -36,13 +36,16 @@ Gem::Specification.new do |s|
|
|
36
36
|
curly-templates.gemspec
|
37
37
|
lib/curly-templates.rb
|
38
38
|
lib/curly.rb
|
39
|
+
lib/curly/attribute_parser.rb
|
39
40
|
lib/curly/compilation_error.rb
|
40
41
|
lib/curly/compiler.rb
|
42
|
+
lib/curly/component_compiler.rb
|
43
|
+
lib/curly/component_parser.rb
|
41
44
|
lib/curly/dependency_tracker.rb
|
42
45
|
lib/curly/error.rb
|
43
46
|
lib/curly/incomplete_block_error.rb
|
44
47
|
lib/curly/incorrect_ending_error.rb
|
45
|
-
lib/curly/
|
48
|
+
lib/curly/invalid_component.rb
|
46
49
|
lib/curly/presenter.rb
|
47
50
|
lib/curly/railtie.rb
|
48
51
|
lib/curly/scanner.rb
|
@@ -52,8 +55,12 @@ Gem::Specification.new do |s|
|
|
52
55
|
lib/generators/curly/controller/templates/presenter.rb.erb
|
53
56
|
lib/generators/curly/controller/templates/view.html.curly.erb
|
54
57
|
lib/rails/projections.json
|
58
|
+
spec/attribute_parser_spec.rb
|
59
|
+
spec/compiler/collections_spec.rb
|
55
60
|
spec/compiler_spec.rb
|
61
|
+
spec/component_compiler_spec.rb
|
56
62
|
spec/generators/controller_generator_spec.rb
|
63
|
+
spec/incorrect_ending_error_spec.rb
|
57
64
|
spec/presenter_spec.rb
|
58
65
|
spec/scanner_spec.rb
|
59
66
|
spec/spec_helper.rb
|
data/lib/curly.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# Curly is a simple view system. Each view consists of two parts, a
|
2
2
|
# template and a presenter. The template is a simple string that can contain
|
3
|
-
#
|
3
|
+
# components in the format `{{refname}}`, e.g.
|
4
4
|
#
|
5
5
|
# Hello {{recipient}},
|
6
6
|
# you owe us ${{amount}}.
|
7
7
|
#
|
8
|
-
# The
|
8
|
+
# The components will be converted into messages that are sent to the
|
9
9
|
# presenter, which is any Ruby object. Only public methods can be referenced.
|
10
10
|
# To continue the earlier example, here's the matching presenter:
|
11
11
|
#
|
@@ -26,7 +26,7 @@
|
|
26
26
|
# See Curly::Presenter for more information on presenters.
|
27
27
|
#
|
28
28
|
module Curly
|
29
|
-
VERSION = "
|
29
|
+
VERSION = "2.0.0.beta1"
|
30
30
|
|
31
31
|
# Compiles a Curly template to Ruby code.
|
32
32
|
#
|
@@ -38,7 +38,7 @@ module Curly
|
|
38
38
|
end
|
39
39
|
|
40
40
|
# Whether the Curly template is valid. This includes whether all
|
41
|
-
#
|
41
|
+
# components are available on the presenter class.
|
42
42
|
#
|
43
43
|
# template - The template String that should be validated.
|
44
44
|
# presenter_class - The presenter Class.
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Curly
|
2
|
+
AttributeError = Class.new(Curly::Error)
|
3
|
+
|
4
|
+
class AttributeParser
|
5
|
+
def self.parse(string)
|
6
|
+
return {} if string.nil?
|
7
|
+
new(string).parse
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(string)
|
11
|
+
@scanner = StringScanner.new(string)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
attributes = scan_attributes
|
16
|
+
Hash[attributes]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def scan_attributes
|
22
|
+
attributes = []
|
23
|
+
|
24
|
+
while attribute = scan_attribute
|
25
|
+
attributes << attribute
|
26
|
+
end
|
27
|
+
|
28
|
+
attributes
|
29
|
+
end
|
30
|
+
|
31
|
+
def scan_attribute
|
32
|
+
skip_whitespace
|
33
|
+
|
34
|
+
return if @scanner.eos?
|
35
|
+
|
36
|
+
name = scan_name or raise AttributeError
|
37
|
+
value = scan_value or raise AttributeError
|
38
|
+
|
39
|
+
[name, value]
|
40
|
+
end
|
41
|
+
|
42
|
+
def scan_name
|
43
|
+
name = @scanner.scan(/\w+=/)
|
44
|
+
name && name[0..-2]
|
45
|
+
end
|
46
|
+
|
47
|
+
def scan_value
|
48
|
+
scan_unquoted_value || scan_single_quoted_value || scan_double_quoted_value
|
49
|
+
end
|
50
|
+
|
51
|
+
def scan_unquoted_value
|
52
|
+
@scanner.scan(/\w+/)
|
53
|
+
end
|
54
|
+
|
55
|
+
def scan_single_quoted_value
|
56
|
+
value = @scanner.scan(/'[^']*'/)
|
57
|
+
value && value[1..-2]
|
58
|
+
end
|
59
|
+
|
60
|
+
def scan_double_quoted_value
|
61
|
+
value = @scanner.scan(/"[^"]*"/)
|
62
|
+
value && value[1..-2]
|
63
|
+
end
|
64
|
+
|
65
|
+
def skip_whitespace
|
66
|
+
@scanner.skip(/\s*/)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|