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