curly-templates 1.0.1 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d72b1af07f110af3c9bfb294cc3187b6d3c50e90
4
- data.tar.gz: e9dac2337a59c5784231de4aafb3055f96722b6c
3
+ metadata.gz: f70027aee738dbd7ac5cc496cce7017943304833
4
+ data.tar.gz: 68947fc8e15e97945fcba195b0c278e14930d9dd
5
5
  SHA512:
6
- metadata.gz: 7a599cadb2fbede5c03f5f650a5c2385540e9280c5414c3f1fef7b3b6978139551f9865320a563cd83e21c5727d90d230a0c852103f0b72b4b0b09eb0ff2b872
7
- data.tar.gz: 9fc5cddddd5cad763b949c308ed35a53cc1ce72f04188e195f16c9a92c9a78054091cf9f0307e00779cf57fe2d6a5c89d3156c0a61efb2f9c38897b14f93219c
6
+ metadata.gz: d3d6816a0d6fb0e6125cc89845bea00ef363e03012f9bc96e083d0d0951febb032b31472a8bcb23271c16333a470a881e2bf0c4129b8712f06e4f5c24aa86c4b
7
+ data.tar.gz: 141f19f518dc381ecde8ac6d451bde20551bdcc711c4a9d1a7aa0f981cb100e1e840feb603a01eed956d5e865c3e6c7ef95d736f6302921fb29d3ad858c3150b
data/CHANGELOG.md CHANGED
@@ -1,9 +1,18 @@
1
- ### Curly 1.0.1 (June 24, 2014)
1
+ ### Unreleased
2
2
 
3
- * Use SortedSet to store view dependencies. We always want these to be in
4
- order.
3
+ ### Curly 2.0.0.beta1 (June 27, 2014)
5
4
 
6
- *Liborio Cannici*
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 variables:
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 filling in the variables. Add the necessary
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 be parameterized by a single argument: `{{#locale.en?}}...{{/locale.en?}}`
120
- will only render the block if the `locale?` method on the presenter returns true given the
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 parameters using the `presents` method:
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 parameter can have a default value:
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$/ || `git branch` =~ /^\* \d+-\d+-stable$/
68
+ unless `git branch` =~ /^\* master$/
69
69
  puts "You must be on the master branch to release!"
70
70
  exit!
71
71
  end
@@ -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 = '1.0.1'
8
- s.date = '2014-06-24'
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/invalid_reference.rb
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
- # references in the format `{{refname}}`, e.g.
3
+ # components in the format `{{refname}}`, e.g.
4
4
  #
5
5
  # Hello {{recipient}},
6
6
  # you owe us ${{amount}}.
7
7
  #
8
- # The references will be converted into messages that are sent to 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 = "1.0.1"
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
- # references are available on the presenter class.
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