curtain 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +311 -17
  3. data/TODO.md +7 -0
  4. data/curtain.gemspec +2 -3
  5. data/erubs_example.rb +103 -0
  6. data/example.erb +4 -0
  7. data/example.slim +2 -0
  8. data/lib/curtain/caching.rb +9 -0
  9. data/lib/curtain/erubis.rb +33 -0
  10. data/lib/curtain/erubis_template.rb +23 -0
  11. data/lib/curtain/form_builder.rb +218 -0
  12. data/lib/curtain/form_helpers.rb +23 -0
  13. data/lib/curtain/html_helpers.rb +180 -0
  14. data/lib/curtain/output_buffer.rb +10 -0
  15. data/lib/curtain/rendering.rb +9 -30
  16. data/lib/curtain/templating.rb +5 -1
  17. data/lib/curtain/version.rb +1 -1
  18. data/lib/curtain.rb +18 -1
  19. data/test/basic_test.rb +66 -0
  20. data/test/cache_test.rb +17 -0
  21. data/test/examples/{body.erb → basic/erb/body.erb} +0 -0
  22. data/test/examples/basic/erb/index.erb +1 -0
  23. data/test/examples/basic/erb/layout.erb +3 -0
  24. data/test/examples/{subdir → basic/erb/subdir}/index.erb +0 -0
  25. data/test/examples/{test.erb → basic/erb/test.erb} +0 -0
  26. data/test/examples/basic/slim/body.slim +5 -0
  27. data/test/examples/basic/slim/index.slim +1 -0
  28. data/test/examples/basic/slim/layout.slim +2 -0
  29. data/test/examples/basic/slim/subdir/index.slim +1 -0
  30. data/test/examples/basic/slim/test.slim +3 -0
  31. data/test/examples/cache/erb/cache.erb +3 -0
  32. data/test/examples/cache/slim/cache.slim +2 -0
  33. data/test/examples/form/Rakefile +56 -0
  34. data/test/examples/form/account.html +101 -0
  35. data/test/examples/form/account.rb +11 -0
  36. data/test/examples/form/account.yml +11 -0
  37. data/test/examples/form/account_view.rb +3 -0
  38. data/test/examples/form/account_with_data.html +100 -0
  39. data/test/examples/form/erb/account.erb +84 -0
  40. data/test/examples/form/erb/account_with_fields.erb +0 -0
  41. data/test/examples/form/erb/bootstrap.erb +15 -0
  42. data/test/examples/form/slim/account.slim +65 -0
  43. data/test/examples/form/slim/bootstrap.slim +11 -0
  44. data/test/examples/html/erb/content_tag.erb +1 -0
  45. data/test/examples/html/erb/content_tag_with_content.erb +1 -0
  46. data/test/examples/html/erb/content_tag_with_content_and_attributes.erb +1 -0
  47. data/test/examples/html/erb/content_tag_with_content_block_and_attributes.erb +3 -0
  48. data/test/examples/html/erb/content_tag_with_content_block_with_nested_tags.erb +3 -0
  49. data/test/examples/html/erb/empty_content_tag.erb +1 -0
  50. data/test/examples/html/erb/void_tag.erb +1 -0
  51. data/test/examples/html/erb/void_tag_with_attributes.erb +1 -0
  52. data/test/examples/html/slim/content_tag.slim +1 -0
  53. data/test/examples/html/slim/content_tag_with_content.slim +1 -0
  54. data/test/examples/html/slim/content_tag_with_content_and_attributes.slim +1 -0
  55. data/test/examples/html/slim/content_tag_with_content_block_and_attributes.slim +2 -0
  56. data/test/examples/html/slim/content_tag_with_content_block_with_nested_tags.slim +2 -0
  57. data/test/examples/html/slim/empty_content_tag.slim +1 -0
  58. data/test/examples/html/slim/void_tag.slim +1 -0
  59. data/test/examples/html/slim/void_tag_with_attributes.slim +1 -0
  60. data/test/form_test.rb +44 -0
  61. data/test/html_test.rb +32 -0
  62. data/test/test_helper.rb +31 -2
  63. metadata +101 -34
  64. data/test/curtain_test.rb +0 -119
  65. data/test/examples/index.erb +0 -1
  66. data/test/examples/layout.erb +0 -1
  67. data/test/examples/registration.mustache +0 -48
  68. data/test/examples/simple.erb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e1156e5774dba04fd48f55a9ba00b80c7ece79e
4
- data.tar.gz: 733abf03c5255f5554bf320cdd7706ab6ce2dac3
3
+ metadata.gz: deb1542f4574f0d23da36aab00e9dc2841c2f1b5
4
+ data.tar.gz: 6fd652b3eae67688a634f9f84b9b9bec81026427
5
5
  SHA512:
6
- metadata.gz: d3e1e00778a9f258d8345ea8c369e82f65e6c0ccec4f31b91664b8a516d69d94fd868f69d7b8a1e9ca105cebc23c2a16ae0ffd1aaa5db3cde97ed766150e5d96
7
- data.tar.gz: b5f2cd8598ff384ed766c8ce38939802f4f6776a82661379c0e72bf81f4406899125895fb2ee4330fd51b8633e34562f3b70e9f40f63b04d871ee70edd120dd5
6
+ metadata.gz: c07f312c43a694d7dc14bfa70f685f9ff23f2f5a86a1739023ec18b4e5c538228ae7e929711862151c433e068401aa51c161554b1ee69e517bd4fb8b9d6ba033
7
+ data.tar.gz: 172fd6af7e87c927aea65a218fdec517901ffc561b57d6fce5bedcb4ce861bf79d4a648b6dc6b4680cc6f6cffd7913bf70b6a43c63283c3379cedc1b894eb9ab
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Curtain is a template rendering framework for Ruby. It is built on top of [Tilt](https://github.com/rtomayko/tilt). Curtain is not tied to any web framework like Rails or Sinatra, so it can be used standalone in any Ruby project.
4
4
 
5
- ## Installation
5
+ # Installation
6
6
 
7
7
  Add this line to your application's Gemfile:
8
8
 
@@ -16,16 +16,20 @@ Or install it yourself as:
16
16
 
17
17
  $ gem install curtain
18
18
 
19
- ## Usage
19
+ # Usage
20
+
21
+ ## Rendering Templates
20
22
 
21
23
  To use Curtain, you define a view and then have that view render templates:
22
24
 
23
- ### hello.erb
24
- ``` erb
25
+ **hello.erb**
26
+
27
+ ``` rhtml
25
28
  <h1><%= msg %></h1>
26
29
  ```
27
30
 
28
- ### my_view.rb
31
+ **my_view.rb**
32
+
29
33
  ``` ruby
30
34
  class MyView
31
35
  include Curtain
@@ -46,21 +50,60 @@ view.render("hello") # => <h1>Hello, World!</h1>
46
50
  The template is rendered in the scope of the view object, so any methods defined in the view are available to the template. You don't have to create a subclass if you don't need to:
47
51
 
48
52
  ``` ruby
49
- Curtain::View.new.render("hello", :msg => "Hello, World!")
53
+ Curtain::View.new.render("hello", msg: "Hello, World!")
50
54
  ```
51
55
 
52
56
  There is an equivalent shortcut available:
53
57
 
54
58
  ``` ruby
55
- Curtain.render("hello", :msg => "Hello, World!")
59
+ Curtain.render("hello", msg: "Hello, World!")
60
+ ```
61
+
62
+ ## Partials
63
+
64
+ Because `render` is a method on the view object, you can call it from within the template to render one template from another:
65
+
66
+ **main.erb**
67
+
68
+ ``` rhtml
69
+ <%= render "partial" %>
56
70
  ```
57
71
 
58
- ### Variables
72
+ **partial.erb**
73
+
74
+ ``` rhtml
75
+ <h2>I'm a Partial!</h2>
76
+ ```
77
+
78
+ ```
79
+ Curtain.render("main") # => "<h2>I'm a Partial!</h2>"
80
+ ```
81
+
82
+ You can also pass local variables to the partial:
83
+
84
+ **main.erb**
85
+
86
+ ``` rhtml
87
+ <%= render "partial", greeting: 'Hello' %>
88
+ ```
59
89
 
60
- If you don't want to define a subclass of `Curtain::View` and add attributes to it, you can also use variables. `Curtain::View` supports the hash-like Ruby method `[]` and `[]=` to define variables that will act as locals in when the template is rendered:
90
+ **partial.erb**
61
91
 
62
- ### hello.erb
63
- ``` erb
92
+ ``` rhtml
93
+ <h2><%= greeting %>, I'm a Partial!</h2>
94
+ ```
95
+
96
+ ```
97
+ Curtain.render("main") # => "<h2>Hello, I'm a Partial!</h2>"
98
+ ```
99
+
100
+ ## Variables
101
+
102
+ If you don't want to define a subclass of `Curtain::View` and add attributes to it, you can also use variables. `Curtain::View` supports the hash-like Ruby methods `[]` and `[]=` to define variables that will act as locals in when the template is rendered:
103
+
104
+ **hello.erb**
105
+
106
+ ``` rhtml
64
107
  <h1><%= msg %></h1>
65
108
  ```
66
109
 
@@ -72,15 +115,17 @@ view.render(:hello) # => "<h1>Hello</h1>"
72
115
 
73
116
  Note that unlike locals, variables exist throughout nested scope of render calls:
74
117
 
75
- ### main.erb
76
- ``` erb
118
+ **main.erb**
119
+
120
+ ``` rhtml
77
121
  foo: <%= foo %>
78
122
  bar: <%= bar %>
79
123
  <%= render "partial" %>
80
124
  ```
81
125
 
82
- ### partial.erb
83
- ``` erb
126
+ **partial.erb**
127
+
128
+ ``` rhtml
84
129
  foo: <%= foo %>
85
130
  bar: <%= bar %>
86
131
  ```
@@ -94,12 +139,261 @@ view[:foo] = "foo"
94
139
  view.render :bar => "bar"
95
140
  ```
96
141
 
97
- This example would result in an error. As the main template is first rendered, foo is defined as "foo" because it is a variable, the bar is "bar" because it is passed in as a local. Then the partial template is rendered, and foo is still defined as "foo" because it is a variable, but since bar was a local passed to the rendering of main, it doesn't carry through to the rendering of partial.
142
+ This example would result in an error. As the main template is first rendered, foo is defined as "foo" because it is a variable, bar is "bar" because it is passed in as a local. Then the partial template is rendered, and foo is still defined as "foo" because it is a variable, but since bar was a local passed to the rendering of main, it doesn't carry through to the rendering of partial.
143
+
144
+ ## HTML Tag Methods
145
+
146
+ *NOTE: The HTML Tag Methods are only supported in [ERB][erb] and [Slim][slim]*
147
+
148
+ `Curtain::View` has HTML tag methods to make generating HTML easier. There is a method for every valid HTML tag. The methods for [void tags][void-tags] take an optional Hash of attributes:
149
+
150
+ **photo.erb**
151
+
152
+ ``` rhtml
153
+ <%= img src: image_path %>
154
+ ```
155
+
156
+ ``` ruby
157
+ Curtain.render('photo', image_path: '/pic.jpg') # => <img src="/pic.jpg">
158
+ ```
159
+
160
+ The methods for tags that may have body content accept the body content as a string in the first argument:
161
+
162
+ **link.erb**
163
+
164
+ ``` rhtml
165
+ <%= a 'Home', href: path %>
166
+ ```
167
+
168
+ ``` ruby
169
+ Curtain.render('link', path: '/') # => <a href="/">Home</a>
170
+ ```
171
+
172
+ or you can pass the body content as a block:
173
+
174
+ **link.erb**
175
+
176
+ ``` rhtml
177
+ <%= a href: path, class: 'btn btn-default' do %>
178
+ <span class="glyphicon glyphicon-home"></span> Home
179
+ <% end %>
180
+ ```
181
+
182
+ ``` ruby
183
+ Curtain.render('link', path: '/') # => <a href="/" class="btn btn-default"><span class="glyphicon glyphicon-home"></span> Home</a>
184
+ ```
185
+
186
+ ## Form Tag Methods
187
+
188
+ There are some extra tag methods related to forms, as well as enhanced functionality for the form-related tags.
189
+
190
+ ### Input Types
191
+
192
+ You can create an `input` tag using the `input` method:
193
+
194
+ ``` rhtml
195
+ <%= input type: 'text', name: 'first_name' %>
196
+ ```
197
+
198
+ but there are shortcut methods for all the valid types for input. Here are some examples:
199
+
200
+ ``` rhtml
201
+ <%= text :first_name %>
202
+ <%= file :photo %>
203
+ <%= email :email %>
204
+ <%= password :password %>
205
+ <%= url :website %>
206
+ ```
207
+
208
+ The equivalent to those examples are:
209
+
210
+ ``` rhtml
211
+ <%= input type: 'text', name: 'first_name' %>
212
+ <%= input type: 'file', name: 'photo' %>
213
+ <%= input type: 'email', name: 'email' %>
214
+ <%= input type: 'password', name: 'password' %>
215
+ <%= input type: 'url', name: 'website' %>
216
+ ```
217
+
218
+ The one exception to this pattern is that this:
219
+
220
+ ``` rhtml
221
+ <%= submit "Save" %>
222
+ ```
223
+
224
+ generates:
225
+
226
+ ``` html
227
+ <button type="submit">Save</button>
228
+ ```
229
+
230
+ ### Forms
231
+
232
+ The `form` tag has some special behavior as well. You can use it without any special behavior with no arguments:
233
+
234
+ ``` rhtml
235
+ <%= form do %>
236
+ <% end %>
237
+ ```
238
+
239
+ which generates:
240
+
241
+ ``` html
242
+ <form>
243
+ </form>
244
+ ```
245
+
246
+ or you can pass a Hash of attributes:
247
+
248
+ ``` rhtml
249
+ <%= form method: 'patch', action: '/register' do %>
250
+ <% end %>
251
+ ```
252
+
253
+ which generates:
254
+
255
+ ``` html
256
+ <form method="post" action="/register">
257
+ <input type="hidden" name="_method" value="patch">
258
+ </form>
259
+ ```
260
+
261
+ Because browsers only natively support `GET` and `POST` methods, a hidden parameter is generated for the method if you use something other than `GET` or `POST`.
98
262
 
99
- ## Contributing
263
+ ### Form Builders
264
+
265
+ When creating forms, it is common to want to have a form field for each attribute of an object and have the value of each input set to the value of the correspoding attribute of the object. You can do that using a Form Builder. Assuming you have something like this in the view:
266
+
267
+ ``` ruby
268
+ @person = Person.new(id: 1, first_name: 'Paul')
269
+ ```
270
+
271
+ You can use a form builder to create a form using the `for` attribute of the `form` tag method:
272
+
273
+ ``` rhtml
274
+ <%= form for: @person do %>
275
+ <%= text :first_name %>
276
+ <% end %>
277
+ ```
278
+
279
+ This generates:
280
+
281
+ ``` html
282
+ <form method="post" action="/people/1">
283
+ <input type="hidden" name="_method" value="PATCH">
284
+ <input type="text" name="person[first_name]" value="Paul">
285
+ </form>
286
+ ```
287
+
288
+ There are a few things that happen when you use the `for` attribute. First, the method and action are inferred based on the object. If the object has an ID, it is assumed to be a record that already been saved, therefore the method will be `patch` and the action will be the pluralized class name with the ID at the end. If the object does not have an ID, it is assumed to be a new record, so the `post` method is used and the action is just the pluralized class name. You can override the generated method by passing explict values for `method` and `action`
289
+
290
+ ``` rhtml
291
+ <%= form for: @person, method: 'post', action: '/profile' do %>
292
+ <% end %>
293
+ ```
294
+
295
+ The second thing that happens when you use a form builder is that the input names are prefixed with the underscored name of the class and the attribute is in brackets. This is so that all parameters related to the object will be collected into one Hash in the parameters. In the example above, `person` is the underscored name of the class and the attribute is `first_name`, so the name of the input ends up being `person[first_name]`. If you want to use something other than the underscored name of the class, you can use the `as` attributesin the `form` method:
296
+
297
+ ``` rhtml
298
+ <%= form for: @person, as: 'account' do %>
299
+ <%= text :first_name %>
300
+ <% end %>
301
+ ```
302
+
303
+ This will result in the input name being `account[first_name]`.
304
+
305
+ Finally, the value is set based on the value of the attribute of the object the form is for. As seen in the example above, `value` is `Paul`, because that is that is the value returned by `@person.first_name`.
306
+
307
+ ### Fields
308
+
309
+ When building forms, it is common to want to have a label and errors messages next to each input. This pattern is supported by the `_field` methods. This example:
310
+
311
+ ``` rhtml
312
+ <%= form for: @person do %>
313
+ <%= text_field :first_name %>
314
+ <% end %>
315
+ ```
316
+
317
+ produces the following HTML:
318
+
319
+ ``` html
320
+ <form method="post" action="/people">
321
+ <div class="form-field">
322
+ <label for="first_name">First Name</label>
323
+ <input type="text" name="first_name" value="" id="first_name">
324
+ <span class="error">is required</span>
325
+ </div>
326
+ </form>
327
+ ```
328
+
329
+ The content of the label can be customized with the `label` attribute. The error only shows up if the form object has an error on that attribute.
330
+
331
+ There is a corresponding `_field` method for each input tag, so `text_field`, `email_field`, `password_field`, etc. There are also `_field` methods for the other non-input form tag methods, such as `select_field` and `textarea_field`.
332
+
333
+ ### Custom Form Builders
334
+
335
+ The markup produced by the `_field` methods is controlled by the form builder. The form builder is a class that can be overriden and customized. You can use a different form builder for a specific form:
336
+
337
+ ``` rhtml
338
+ <%= form builder: MyFormBuilder do %>
339
+ ```
340
+
341
+ The class passed to the `builder` attribute should be a subclass of `Curtain::FormBuilder`. You can also control which form builder is used in a more broad manner. The default form builder is determined by the `default_form_builder` method on the `View` class. `Curtain::View.default_form_builder` returns `Curtain::FormBuilder`. You can set the default form builder on your view like this:
342
+
343
+ ``` ruby
344
+ class MyView < Curtain::View
345
+ default_form_builder MyFormBuilder
346
+ end
347
+ ```
348
+
349
+ This works throughout the inhereitence chain, so you can create a single view in your application that all views inheret from and then set the default_form_builder on that view in order to make all forms in your application use your custom form builder.
350
+
351
+ ### Pre-built Form Builders
352
+
353
+ There are already form builders for the [Bootstrap][bootstrap] and [Foundation][foundation] CSS frameworks. To use them, install the gem for the one you wish to use, [curtain-bootstrap][curtain-bootstrap] or [curtain-foundation][curtain-foundation], and then set the default form builder on the view you would like to use them on:
354
+
355
+ ``` ruby
356
+ class ApplicationView < Curtain::View
357
+ default_form_builder Curtain::Bootstrap::FormBuilder
358
+ end
359
+ ```
360
+
361
+ See the documentation for each of those projects to see exactly what markup they generate.
362
+
363
+ ## Caching
364
+
365
+ Curtain has built-in support for caching via the `cache` method. You use the method like this:
366
+
367
+ ``` rhtml
368
+ <%= cache 'hourly-stats', expires_in: 1.hour do %>
369
+ <% get_report_data.each do |row| %>
370
+ ...
371
+ <% end %>
372
+ <% end %>
373
+ ```
374
+
375
+ To use caching, you just set the cache on the view class:
376
+
377
+ ``` ruby
378
+ Curtain::View.cache = Dalli::Client.new
379
+ ```
380
+
381
+ You can use anything supported by the [Cache][cache] library for the cache.
382
+
383
+ # Contributing
100
384
 
101
385
  1. Fork it
102
386
  2. Create your feature branch (`git checkout -b my-new-feature`)
103
387
  3. Commit your changes (`git commit -am 'Added some feature'`)
104
388
  4. Push to the branch (`git push origin my-new-feature`)
105
389
  5. Create new Pull Request
390
+
391
+ [tilt]: http://github.com/rtomayko/tilt
392
+ [erb]: http://www.kuwata-lab.com/erubis/#Introduction
393
+ [slim]: http://slim-lang.com
394
+ [bootstrap]: http://getbootstrap.com
395
+ [foundation]: http://foundation.zurb.com
396
+ [curtain-bootstrap]: http://rubygems.org/gems/curtain-bootstrap
397
+ [curtain-foundation]: http://rubygems.org/gems/curtain-foundation
398
+ [cache]: http://rubygems.org/gems/cache
399
+ [void-tags]: http://www.w3.org/TR/html-markup/syntax.html#void-element
data/TODO.md ADDED
@@ -0,0 +1,7 @@
1
+ # TODO
2
+
3
+ * add hidden input for method
4
+ * prefix form field names
5
+ * some sort of variant of the input tags that generates the full row div, with a label and errors
6
+ * Bootstrap and Foundation form builders
7
+ * implement caching
data/curtain.gemspec CHANGED
@@ -12,13 +12,12 @@ Gem::Specification.new do |gem|
12
12
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
13
  gem.name = "curtain"
14
14
  gem.require_paths = ["lib"]
15
- gem.version = "0.2.0"
15
+ gem.version = "0.3.0"
16
16
 
17
17
  gem.add_runtime_dependency "activesupport"
18
18
  gem.add_runtime_dependency "tilt"
19
19
 
20
20
  gem.add_development_dependency "erubis"
21
- gem.add_development_dependency "haml"
21
+ gem.add_development_dependency "glam"
22
22
  gem.add_development_dependency "slim"
23
- gem.add_development_dependency "mustache"
24
23
  end
data/erubs_example.rb ADDED
@@ -0,0 +1,103 @@
1
+ require 'active_support/core_ext'
2
+ require 'erubis'
3
+ require 'tilt'
4
+ require 'temple'
5
+
6
+ module Curtain
7
+ class OutputBuffer < ActiveSupport::SafeBuffer
8
+ def <<(value)
9
+ super(value.to_s)
10
+ end
11
+ alias :append= :<<
12
+ alias :safe_append= :<<
13
+ alias :safe_contact :<<
14
+ end
15
+ end
16
+
17
+ module Curtain
18
+ class Erubis < ::Erubis::Eruby
19
+ def add_text(src, text)
20
+ return if text.empty?
21
+ src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
22
+ end
23
+
24
+ BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
25
+
26
+ def add_expr_literal(src, code)
27
+ if code =~ BLOCK_EXPR
28
+ src << '@output_buffer.append= ' << code
29
+ else
30
+ src << '@output_buffer.append= (' << code << ');'
31
+ end
32
+ end
33
+
34
+ def add_expr_escaped(src, code)
35
+ if code =~ BLOCK_EXPR
36
+ src << "@output_buffer.safe_append= " << code
37
+ else
38
+ src << "@output_buffer.safe_concat((" << code << ").to_s);"
39
+ end
40
+ end
41
+
42
+ def add_postamble(src)
43
+ src << '@output_buffer.to_s'
44
+ end
45
+ end
46
+ end
47
+
48
+ module Curtain
49
+ class ErubisTemplate < Tilt::Template
50
+ DEFAULT_OUTPUT_VARIABLE = '@output_buffer'
51
+
52
+ def self.engine_initialized?
53
+ defined? ::ERB
54
+ end
55
+
56
+ def initialize_engine
57
+ require_template_library 'erubis'
58
+ end
59
+
60
+ def prepare
61
+ @engine = Curtain::Erubis.new(data, options)
62
+ end
63
+
64
+ def precompiled_template(locals)
65
+ @engine.src
66
+ end
67
+ end
68
+ end
69
+
70
+ Tilt.prefer Curtain::ErubisTemplate, 'erb'
71
+ class View
72
+ attr_accessor :output_buffer
73
+
74
+ def initialize
75
+ @output_buffer = Curtain::OutputBuffer.new
76
+ end
77
+
78
+ def capture
79
+ original_buffer = @output_buffer
80
+ @output_buffer = Curtain::OutputBuffer.new
81
+ yield
82
+ ensure
83
+ @output_buffer = original_buffer
84
+ end
85
+
86
+ def form(attrs={}, &body)
87
+ %{<form>#{capture(&body)}</form>}.html_safe
88
+ end
89
+
90
+ def input(attrs={})
91
+ %{<input#{attrs.map{|n,v| %{ #{n}="#{v}"} }.join}/>}.html_safe
92
+ end
93
+
94
+ def a(attrs={}, &body)
95
+ %{<a#{Array(attrs).map{|n,v| %{ #{n}="#{v}"} }.join}>#{capture(&body)}</a>}.html_safe
96
+ end
97
+ end
98
+
99
+ template = Tilt.new('example.erb', :buffer => '@output_buffer', :use_html_safe => true, :disable_capture => true, :generator => Temple::Generators::RailsOutputBuffer)
100
+ puts template.render(View.new)
101
+
102
+ template = Tilt.new('example.slim', :buffer => '@output_buffer', :use_html_safe => true, :disable_capture => true, :generator => Temple::Generators::RailsOutputBuffer)
103
+ puts template.render(View.new)
data/example.erb ADDED
@@ -0,0 +1,4 @@
1
+ <%= form do %>
2
+ <%= input type: 'text', name: 'first_name' %>
3
+ <%= a href: '/' do %>Home<% end %>
4
+ <% end %>
data/example.slim ADDED
@@ -0,0 +1,2 @@
1
+ = form
2
+ = input type: 'text', name: 'first_name'
@@ -0,0 +1,9 @@
1
+ module Curtain
2
+ module Caching
3
+ def cache(key, opts={}, &body)
4
+ content = capture(&body)
5
+ # TODO: Implement Me!
6
+ content.html_safe
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ require 'erubis'
2
+ require 'curtain/erubis_template'
3
+
4
+ module Curtain
5
+ class Erubis < ::Erubis::Eruby
6
+ def add_text(src, text)
7
+ return if text.empty?
8
+ src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
9
+ end
10
+
11
+ BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
12
+
13
+ def add_expr_literal(src, code)
14
+ if code =~ BLOCK_EXPR
15
+ src << '@output_buffer.append= ' << code
16
+ else
17
+ src << '@output_buffer.append= (' << code << ');'
18
+ end
19
+ end
20
+
21
+ def add_expr_escaped(src, code)
22
+ if code =~ BLOCK_EXPR
23
+ src << "@output_buffer.safe_append= " << code
24
+ else
25
+ src << "@output_buffer.safe_concat((" << code << ").to_s);"
26
+ end
27
+ end
28
+
29
+ def add_postamble(src)
30
+ src << '@output_buffer.to_s'
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ module Curtain
2
+ class ErubisTemplate < Tilt::Template
3
+ DEFAULT_OUTPUT_VARIABLE = '@output_buffer'
4
+
5
+ def self.engine_initialized?
6
+ defined? ::ERB
7
+ end
8
+
9
+ def initialize_engine
10
+ require_template_library 'erubis'
11
+ end
12
+
13
+ def prepare
14
+ @engine = Curtain::Erubis.new(data, options)
15
+ end
16
+
17
+ def precompiled_template(locals)
18
+ @engine.src
19
+ end
20
+ end
21
+ end
22
+
23
+ Tilt.prefer Curtain::ErubisTemplate, 'erb'