inversion 0.12.3 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -1
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +305 -9
  5. data/Examples.rdoc +134 -0
  6. data/GettingStarted.rdoc +44 -0
  7. data/Guide.rdoc +47 -0
  8. data/History.rdoc +15 -0
  9. data/Manifest.txt +7 -2
  10. data/README.rdoc +9 -10
  11. data/Rakefile +23 -10
  12. data/Tags.rdoc +561 -0
  13. data/lib/inversion.rb +2 -2
  14. data/lib/inversion/renderstate.rb +46 -11
  15. data/lib/inversion/template.rb +85 -7
  16. data/lib/inversion/template/attrtag.rb +1 -1
  17. data/lib/inversion/template/begintag.rb +8 -8
  18. data/lib/inversion/template/fragmenttag.rb +60 -0
  19. data/lib/inversion/template/rescuetag.rb +1 -1
  20. data/spec/{lib/helpers.rb → helpers.rb} +7 -30
  21. data/spec/inversion/mixins_spec.rb +55 -65
  22. data/spec/inversion/monkeypatches_spec.rb +2 -12
  23. data/spec/inversion/parser_spec.rb +34 -44
  24. data/spec/inversion/renderstate_spec.rb +123 -69
  25. data/spec/inversion/sinatra_spec.rb +6 -19
  26. data/spec/inversion/template/attrtag_spec.rb +56 -76
  27. data/spec/inversion/template/begintag_spec.rb +24 -41
  28. data/spec/inversion/template/calltag_spec.rb +1 -18
  29. data/spec/inversion/template/codetag_spec.rb +6 -24
  30. data/spec/inversion/template/commenttag_spec.rb +9 -27
  31. data/spec/inversion/template/configtag_spec.rb +5 -16
  32. data/spec/inversion/template/containertag_spec.rb +4 -21
  33. data/spec/inversion/template/defaulttag_spec.rb +6 -23
  34. data/spec/inversion/template/elsetag_spec.rb +9 -26
  35. data/spec/inversion/template/elsiftag_spec.rb +7 -24
  36. data/spec/inversion/template/endtag_spec.rb +6 -23
  37. data/spec/inversion/template/escapetag_spec.rb +10 -25
  38. data/spec/inversion/template/fortag_spec.rb +20 -37
  39. data/spec/inversion/template/fragmenttag_spec.rb +40 -0
  40. data/spec/inversion/template/iftag_spec.rb +23 -40
  41. data/spec/inversion/template/importtag_spec.rb +8 -25
  42. data/spec/inversion/template/includetag_spec.rb +27 -42
  43. data/spec/inversion/template/node_spec.rb +6 -15
  44. data/spec/inversion/template/pptag_spec.rb +10 -23
  45. data/spec/inversion/template/publishtag_spec.rb +4 -21
  46. data/spec/inversion/template/rescuetag_spec.rb +12 -29
  47. data/spec/inversion/template/subscribetag_spec.rb +8 -25
  48. data/spec/inversion/template/tag_spec.rb +24 -37
  49. data/spec/inversion/template/textnode_spec.rb +8 -24
  50. data/spec/inversion/template/timedeltatag_spec.rb +31 -43
  51. data/spec/inversion/template/unlesstag_spec.rb +7 -24
  52. data/spec/inversion/template/uriencodetag_spec.rb +6 -23
  53. data/spec/inversion/template/yieldtag_spec.rb +3 -20
  54. data/spec/inversion/template_spec.rb +155 -108
  55. data/spec/inversion/tilt_spec.rb +7 -16
  56. data/spec/inversion_spec.rb +7 -22
  57. metadata +63 -40
  58. metadata.gz.sig +0 -0
  59. data/spec/lib/constants.rb +0 -9
@@ -0,0 +1,44 @@
1
+ = Getting Started
2
+
3
+ == Requirements
4
+
5
+ - Ruby 1.9.2 or later
6
+
7
+ == Installation
8
+
9
+ $ gem install inversion
10
+
11
+ == Basic Usage
12
+
13
+ Inversion, like most other templating systems, works by giving you a way of defining the static
14
+ parts of your output, and then letting you combine that at a later point with the dynamic parts:
15
+
16
+ Create the template and use it to render an exciting message:
17
+
18
+ tmpl = Inversion::Template.new( "Hello, <?attr name ?>!" )
19
+ tmpl.name = "World"
20
+ puts tmpl.render
21
+
22
+ The +<?attr name ?>+ tag defines the _name_ accessor on the template[rdoc-ref:Templates]
23
+ object, the value of which is substituted for any occurrences of +name+ in the template:
24
+
25
+ Hello, World!
26
+
27
+ This by itself isn't fantastically useful, but it does illustrate one of the ways in which Inversion
28
+ is different: the program and the template share data through an API, instead of through a complex
29
+ data structure, which establishes a clear delineation between what responsibility is the program's
30
+ and which is the template's. The program doesn't have to know how the view uses the data it's given,
31
+ and tests of the controller can substitute a Mock Object for the template to test the interaction
32
+ between the two instead of having to match patterns in the eventual output like an integration test.
33
+
34
+ You can also interact with the values set in the template:
35
+
36
+ Name: <?attr employee.full_name ?>
37
+
38
+ This will call the #full_name method on whatever is set as the +employee+ attribute when
39
+ rendered, and the result will take the place of the tag.
40
+
41
+ Inversion also comes with a collection of {other tags}[rdoc-ref:Tags] that
42
+ provide flow control, exception-handling, etc.
43
+
44
+
data/Guide.rdoc ADDED
@@ -0,0 +1,47 @@
1
+ = Inversion User's Guide
2
+
3
+ Inversion is a templating system for Ruby. It uses the Inversion of Control principle to decouple
4
+ the contents and structure of the template from the code that uses it, making it easier to use,
5
+ test-friendly, and clean.
6
+
7
+ {Getting Started}[rdoc-ref:GettingStarted] goes over the requirements, installation, and basic usage of the library.
8
+
9
+ The rdoc-ref:Templates topic has more details about how to load templates and control their behavior with options.
10
+
11
+ When you're ready to start making your own templates, there's a quick-reference for the list of built-in rdoc-ref:Tags, too.
12
+
13
+ Finally, there are a number of {Annotated Examples}[rdoc-ref:Examples] that illustrate how to solve common problems while templating.
14
+
15
+
16
+ == Authors
17
+
18
+ * Michael Granger
19
+ * Mahlon E. Smith
20
+
21
+
22
+ == License
23
+
24
+ Copyright © 2011-2013, Michael Granger, Mahlon E. Smith
25
+ All rights reserved.
26
+
27
+ Redistribution and use in source and binary forms, with or without modification, are
28
+ permitted provided that the following conditions are met:
29
+
30
+ * Redistributions of source code must retain the above copyright notice, this list of
31
+ conditions and the following disclaimer.
32
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
33
+ conditions and the following disclaimer in the documentation and/or other materials
34
+ provided with the distribution.
35
+ * Neither the name of the authors nor contributors may be used to endorse or promote products
36
+ derived from this software without specific prior written permission.
37
+
38
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
39
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
40
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
41
+ COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
44
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
45
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
46
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47
+
data/History.rdoc CHANGED
@@ -1,3 +1,18 @@
1
+ == v0.14.0 [2014-11-05] Michael Granger <ged@FaerieMUD.org>
2
+
3
+ - Add the fragment tag and docs
4
+ - Remove old manual doc artifacts. Add documentation for the
5
+ 'begin/rescue' and 'default' tags.
6
+
7
+
8
+ == v0.13.0 [2014-04-23] Michael Granger <ged@FaerieMUD.org>
9
+
10
+ - Carry global configuration into instantiated template options.
11
+ - Documentation update.
12
+
13
+ (Never released.)
14
+
15
+
1
16
  == v0.12.3 [2013-09-20] Michael Granger <ged@FaerieMUD.org>
2
17
 
3
18
  - Don't deep_copy IOs or Tempfiles (bugfix).
data/Manifest.txt CHANGED
@@ -1,8 +1,12 @@
1
1
  ChangeLog
2
+ Examples.rdoc
3
+ GettingStarted.rdoc
4
+ Guide.rdoc
2
5
  History.rdoc
3
6
  Manifest.txt
4
7
  README.rdoc
5
8
  Rakefile
9
+ Tags.rdoc
6
10
  bin/inversion
7
11
  lib/inversion.rb
8
12
  lib/inversion/exceptions.rb
@@ -25,6 +29,7 @@ lib/inversion/template/elsiftag.rb
25
29
  lib/inversion/template/endtag.rb
26
30
  lib/inversion/template/escapetag.rb
27
31
  lib/inversion/template/fortag.rb
32
+ lib/inversion/template/fragmenttag.rb
28
33
  lib/inversion/template/iftag.rb
29
34
  lib/inversion/template/importtag.rb
30
35
  lib/inversion/template/includetag.rb
@@ -42,6 +47,7 @@ lib/inversion/template/yieldtag.rb
42
47
  lib/inversion/tilt.rb
43
48
  spec/data/sinatra/hello.inversion
44
49
  spec/data/unknown-tag.tmpl
50
+ spec/helpers.rb
45
51
  spec/inversion/mixins_spec.rb
46
52
  spec/inversion/monkeypatches_spec.rb
47
53
  spec/inversion/parser_spec.rb
@@ -60,6 +66,7 @@ spec/inversion/template/elsiftag_spec.rb
60
66
  spec/inversion/template/endtag_spec.rb
61
67
  spec/inversion/template/escapetag_spec.rb
62
68
  spec/inversion/template/fortag_spec.rb
69
+ spec/inversion/template/fragmenttag_spec.rb
63
70
  spec/inversion/template/iftag_spec.rb
64
71
  spec/inversion/template/importtag_spec.rb
65
72
  spec/inversion/template/includetag_spec.rb
@@ -77,5 +84,3 @@ spec/inversion/template/yieldtag_spec.rb
77
84
  spec/inversion/template_spec.rb
78
85
  spec/inversion/tilt_spec.rb
79
86
  spec/inversion_spec.rb
80
- spec/lib/constants.rb
81
- spec/lib/helpers.rb
data/README.rdoc CHANGED
@@ -47,12 +47,12 @@ You can also interact with the values set in the template:
47
47
  This will call the +#full_name+ method on whatever is set as the +employee+
48
48
  attribute when rendered, and the result will take the place of the tag.
49
49
 
50
- Inversion also comes with a collection of other tags that provide flow
51
- control, exception-handling, etc.
50
+ Inversion also comes with {a collection of other tags}[rdoc-ref:Tags] that
51
+ provide flow control, exception-handling, etc.
52
52
 
53
53
  Here's a slightly more complex example: Say we have a layout template that
54
54
  contains all the boilerplate, navigation, etc. for the site, and then an
55
- `<?attr body ?>` somewhere in the content area for the content specific to
55
+ <code><?attr body ?></code> somewhere in the content area for the content specific to
56
56
  each view:
57
57
 
58
58
  layout = Inversion::Template.load( 'templates/layout.tmpl' )
@@ -69,8 +69,8 @@ Then there's a view template that displays a bulleted list of article titles:
69
69
  </section>
70
70
 
71
71
  Loading this template results in a Ruby object whose API contains one method:
72
- `#articles`. To render the view, we just call that accessor with instances of
73
- an `Article` domain class we defined elsewhere, and then drop the 'alist'
72
+ +#articles+. To render the view, we just call that accessor with instances of
73
+ an +Article+ domain class we defined elsewhere, and then drop the +alist+
74
74
  template into the layout and render them:
75
75
 
76
76
  alist = Inversion::Template.load( 'templates/alist.tmpl' )
@@ -79,14 +79,13 @@ template into the layout and render them:
79
79
  layout.body = alist
80
80
  puts layout.render
81
81
 
82
- The `for` tag in the alist will iterate over the enumerable Articles and
83
- generate an `<li>` for each one. The resulting template object will be set as
82
+ The +for+ tag in the alist will iterate over the enumerable Articles and
83
+ generate an +<li>+ for each one. The resulting template object will be set as
84
84
  the body of the layout template, and stringified when the enclosing template
85
85
  is rendered. Templates can be nested this way as deeply as you like.
86
86
 
87
- For detailed tag documentation and examples, see the {online
88
- manual}[http://deveiate.org/code/Inversion-manual/] or browse {the API
89
- docs}[http://deveiate.org/code/inversion].
87
+ For detailed tag documentation and examples, start with the Inversion::Template
88
+ class in the API documentation.
90
89
 
91
90
 
92
91
  == References
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env rake
2
2
 
3
+ require 'rdoc/task'
4
+
3
5
  begin
4
6
  require 'hoe'
5
7
  rescue LoadError
@@ -22,17 +24,18 @@ hoespec = Hoe.spec 'inversion' do
22
24
  self.developer 'Michael Granger', 'ged@FaerieMUD.org'
23
25
  self.developer 'Mahlon E. Smith', 'mahlon@martini.nu'
24
26
 
25
- self.dependency 'loggability', '~> 0.4'
27
+ self.dependency 'loggability', '~> 0.11'
26
28
 
27
- self.dependency 'highline', '~> 1.6', :development
28
- self.dependency 'hoe-deveiate', '~> 0.3', :development
29
- self.dependency 'hoe-bundler', '~> 1.2', :development
30
- self.dependency 'rack-test', '~> 0.6', :development
31
- self.dependency 'simplecov', '~> 0.6', :development
32
- self.dependency 'sinatra', '~> 1.3', :development
33
- self.dependency 'tilt', '~> 1.4', :development
34
- self.dependency 'sysexits', '~> 1.0', :development
35
- self.dependency 'trollop', '~> 1.16', :development
29
+ self.dependency 'highline', '~> 1.6', :development
30
+ self.dependency 'hoe-deveiate', '~> 0.5', :development
31
+ self.dependency 'hoe-bundler', '~> 1.2', :development
32
+ self.dependency 'rack-test', '~> 0.6', :development
33
+ self.dependency 'simplecov', '~> 0.8', :development
34
+ self.dependency 'sinatra', '~> 1.4', :development
35
+ self.dependency 'tilt', '~> 2.0', :development
36
+ self.dependency 'sysexits', '~> 1.0', :development
37
+ self.dependency 'trollop', '~> 2.0', :development
38
+ self.dependency 'rdoc-generator-fivefish', '~> 0', :development
36
39
 
37
40
  self.require_ruby_version( '>=1.9.2' )
38
41
  self.hg_sign_tags = true if self.respond_to?( :hg_sign_tags= )
@@ -58,3 +61,13 @@ task :coverage do
58
61
  Rake::Task[:spec].invoke
59
62
  end
60
63
 
64
+
65
+ Rake::Task[ 'docs' ].clear
66
+ RDoc::Task.new( 'docs' ) do |rdoc|
67
+ rdoc.main = "README.rdoc"
68
+ rdoc.rdoc_files.include( "*.rdoc", "ChangeLog", "lib/**/*.rb" )
69
+ rdoc.generator = :fivefish
70
+ rdoc.rdoc_dir = 'doc'
71
+ end
72
+
73
+
data/Tags.rdoc ADDED
@@ -0,0 +1,561 @@
1
+ = Built-In Tags
2
+
3
+ Inversion's tags support the Pluggability[http://rubygems.org/gems/pluggability] API, allowing for the easy addition of {custom tags}[@Custom+Tags], but it comes with a number of built-in ones too.
4
+
5
+
6
+ == Tag Syntax
7
+
8
+ Tags can be in either of two formats:
9
+
10
+ - XML Pre-processing instruction style: <code><?tagname tagdata ?></code>
11
+ - or the same thing, but with square brackets instead: <code>[?tagname tagdata ?]</code>
12
+
13
+ The second form is especially useful if you're generating HTML and want to put an Inversion tag inside the attribute of an HTML tag, but still want the template to be well-formed:
14
+
15
+
16
+ <a href="[?call article.permalink ?]">Permalink</a>
17
+
18
+ You can mix tag forms in a single document.
19
+
20
+
21
+ == Tags
22
+
23
+ === begin/rescue
24
+
25
+ These tags work as you'd expect from their ruby counterparts.
26
+
27
+ The +begin+ section of the template to be rendered only if no exceptions are raised while it's
28
+ being rendered. If an exception is raised, it is checked against any +rescue+ sections, and the
29
+ first with a matching exception is rendered instead. If no +rescue+ block is found, the exception
30
+ is handled by the configured exception behavior for the template (see options[Templates@Template+Options]).
31
+
32
+ <?begin ?><?call employees.length ?><?end?>
33
+
34
+ <?begin ?>
35
+ <?for employee in employees.all ?>
36
+ <?attr employee.name ?> --> <?attr employee.title ?>
37
+ <?end for?>
38
+ <?rescue DatabaseError => err ?>
39
+ Oh no!! I can't talk to the database for some reason. The
40
+ error was as follows:
41
+ <pre>
42
+ <?attr err.message ?>
43
+ </pre>
44
+ <?end?>
45
+
46
+ === comment
47
+
48
+ The +comment+ tag can be used to temporarily stop a section of a document from being rendered:
49
+
50
+ <?comment?>
51
+ This stuff won't be rendered.
52
+ <?end comment ?>
53
+ But this stuff will.
54
+
55
+ Note that the tags inside the comment will still be parsed, so the template's interface won't be affected by the comment:
56
+
57
+ <?comment?>
58
+ <?attr content ?>
59
+ <?end comment ?>
60
+
61
+ If you have +debugging_comments+ enabled in the template options[rdoc-ref:Templates@Template+Options], sections which are commented out will be rendered as a comment describing what was omitted, surrounded by the comment characters set in +comment_start+ and +comment_end+ options, respectively.
62
+
63
+ <!-- Commented out 1 nodes on line 1 -->
64
+
65
+ You can also embed a description of why the section is commented in the tag body, which will be used as a label when rendering the comment. For example:
66
+
67
+ <?comment Disabled until there's content ?>
68
+ <?attr content ?>
69
+ <?end comment ?>
70
+
71
+ will be rendered as:
72
+
73
+ <!-- Commented out 1 nodes on line 1: Disabled until there's content -->
74
+
75
+
76
+ === config
77
+
78
+ The +config+ tag can be used to override template options[Templates@Template+Options]
79
+ on a per-template basis. It allows for convenient, inline settings from
80
+ within a template rather than from the code in which the template is
81
+ loaded.
82
+
83
+ For example, if you want to enable debugging comments on a single template:
84
+
85
+ <?config debugging_comments: true ?>
86
+
87
+ Multiple template options can be set simultaneously by using a YAML hash:
88
+
89
+ <?config
90
+ on_render_error: propagate
91
+ debugging_comments: true
92
+ comment_start: /*
93
+ comment_end: */
94
+ ?>
95
+
96
+ Note that this also allows you to set multiple options on a single line, if you wrap them in braces:
97
+
98
+ <?config { comment_start: "/*", comment_end: "*/" } ?>
99
+
100
+
101
+ === default
102
+
103
+ The +default+ tag sets an attribute from within the template, and this value is used if the attribute is otherwise unset.
104
+
105
+ template = Inversion::Template.new <<-TMPL
106
+ <?default adjective to "cruel" ?>
107
+ <?default noun to "world" ?>
108
+ Goodbye, <?attr adjective ?> <?attr noun ?>!
109
+ TMPL
110
+
111
+ template.render
112
+
113
+ template.adjective = "delicious"
114
+ template.render
115
+
116
+ template.adjective = nil
117
+ template.noun = "banana"
118
+ template.render
119
+
120
+ Would produce the output:
121
+
122
+ Goodbye, cruel world!
123
+ Goodbye, delicious world!
124
+ Goodbye, cruel banana!
125
+
126
+
127
+ === fragment
128
+
129
+ A +fragment+ tag also sets an attribute from within the template, but under the scope of the global
130
+ template itself. A fragment can use other Inversion tags, and the attribute is both usable
131
+ elsewhere in the template, and accessible from calling code after rendering.
132
+
133
+ template = Inversion::Template.new <<-TMPL
134
+ <?fragment subject ?>Your order status (Order #<?call order.number ?>)<?end ?>
135
+
136
+ Dear <?call order.customer.name ?>,
137
+
138
+ Your recent order was modified by our Order Fulfillment Team.
139
+
140
+ After careful deliberation, it was decided that no one should have need for that many hot dogs
141
+ with overnight shipping. Frankly, we're more than a little concerned for your health.
142
+
143
+ Sincerely,
144
+ Rowe's Meat Emporium
145
+ (Buy! Sell! Consignment!)
146
+ TMPL
147
+
148
+ template.order = order
149
+ template.render
150
+
151
+ template.fragments[ :subject ] #=> "Your order status (Order #3492)"
152
+
153
+
154
+ == Placeholder Tags
155
+
156
+ Placeholder tags represent the main functionality of Inversion; they create a placeholder in the output text which can be filled in via a method on the template object with the same name.
157
+
158
+
159
+ === attr
160
+
161
+ The +attr+ tag is the primary placeholder tag for injecting dynamic values into your templates. The most basic form is analogous to +attr_accessor+; it defines a method on the template object that, when set, replaces all occurrences of the tag in the template with the value:
162
+
163
+ Title: <?attr title ?>
164
+
165
+ Calling the template object's +#title=+ method will inject the stringified value into that part of the output when it's rendered, e.g.,
166
+
167
+ template.title = "How to Breed Kangaroos for Milk and Meat"
168
+ template.render
169
+ # => "Title: How to Breed Kangaroos for Milk and Meat"
170
+
171
+ The rendered values of an +attr+ tag can also be the result of calling methods on the attr value:
172
+
173
+ ISBN: <?attr book.isbn ?>
174
+
175
+ Attributes can be sprintf formatted using Ruby's String#% method:
176
+
177
+ Book price: <?attr "%0.2f" % book.price ?>
178
+
179
+ Attributes can also contain other template objects, which allows templates to be nested within each other easily.
180
+
181
+ layout = Inversion::Template.load( 'layout.tmpl' )
182
+ content = Inversion::Template.load( 'content.tmpl' )
183
+ content.caption = "Your kids will love their new Kangaroo family!"
184
+ layout.body = content
185
+ layout.render
186
+
187
+
188
+ === call
189
+
190
+ +call+ is just an alias for +attr+. Use whichever strikes your fancy.
191
+
192
+
193
+ === escape
194
+
195
+ +escape+ works just like +attr+, but it escapes the content inserted into the template, using the configured escaping behavior. The supported escaping behaviors are defined in a mixin called Inversion::Escaping. The behavior to use can be set using the {:escape_format}[Templates@Template+Options] option on the template or in a +config+ tag; it defaults to HTML escaping.
196
+
197
+ <p>Company name: <?escape company.name ?></p>
198
+
199
+ If the company was +"AT&T"+, the output would look like:
200
+
201
+ <p>Company name: AT&amp;T</p>
202
+
203
+
204
+ === uriencode
205
+
206
+ The +urlencode+ tag is another +attr+-like tag, but this one does URI encoding:
207
+
208
+ <nav>Edit <a href="/profile?name=[?uriencode person.name ?]">your profile</a></nav>
209
+
210
+
211
+ == Special Placeholders
212
+
213
+ === timedelta
214
+
215
+ If you need to automatically generate a human-readable description of the interval between two times, you can use the +timedelta+ tag:
216
+
217
+ <article class="blogentry">
218
+ <header>
219
+ <p>Posted: <?timedelta entry.date_posted ?>.</p>
220
+ </header>
221
+ ...
222
+ </article>
223
+
224
+ The tag supports any object which responds to the +#to_time+ method, so standard +Time+, +Date+, and +DateTime+ objects all work.
225
+
226
+ Dates are compared against the current time, and render to approximate descriptions of the interval, e.g.,
227
+
228
+ * 4 days ago
229
+ * about an hour from now
230
+ * 6 weeks ago
231
+ * less than a minute from now
232
+
233
+
234
+ == Import and Publish/Subscribe
235
+
236
+ Both of these tags operate on nested templates: one copies objects from an outer template into an inner one (+import+), and the other publishes sections of content from one template to any other subscribed template.
237
+
238
+
239
+ === import
240
+
241
+ Occasionally, you'll want to compose output from several different templates by nesting them, but you don't want to have to set common objects on all of them from code. The +import+ tag lets you copy the values from a container template into one intended to be nested within it:
242
+
243
+ <!-- layout.tmpl -->
244
+ Logged in as: <?attr request.authenticated_user ?>
245
+ <?attr body ?>
246
+
247
+ <!-- body.tmpl -->
248
+ <?import request ?>
249
+ <p>You can check your balance using <a href="[?call request.path_info ?]/accounts">the
250
+ accounts tool</a>.</p>
251
+
252
+ When the content template is nested in the container, you only need to set the +request+ attribute on the container to set it in both places:
253
+
254
+ layout = Inversion::Template.load( 'layout.tmpl' )
255
+ body = Inversion::Template.load( 'body.tmpl' )
256
+
257
+ layout.body = body
258
+ layout.request = request
259
+
260
+ puts layout.render
261
+
262
+ Without the use of +import+, you'd need to similarly set the request attribute on the body template.
263
+
264
+ The imported attribute's value is determined at render time, so you can also use it to import values from an iteration.
265
+
266
+ <!-- Container template (table.tmpl)" -->
267
+ <table>
268
+ <thead>...</thead>
269
+ <tbody>
270
+ <?for user in assigned_users ?>
271
+ <?attr row ?>
272
+ <?end for ?>
273
+ </tbody>
274
+ </table>
275
+ <?end?>
276
+
277
+ <!-- Content template (row.tmpl)" -->
278
+ <?import user ?>
279
+ <tr>
280
+ <th>Username:</th><td><?escape user.username ?></td>
281
+ <th>UID:</th><td><?escape user.uid ?></td>
282
+ <th>GID:</th><td><?escape user.gid ?></td>
283
+ </tr>
284
+ <?end?>
285
+
286
+ and the code:
287
+
288
+ usertable = Inversion::Template.load( 'table.tmpl' )
289
+ userrow = Inversion::Template.load( 'row.tmpl' )
290
+
291
+ usertable.row = userrow
292
+ usertable.assigned_users = User.assigned.all
293
+
294
+ puts usertable.render
295
+
296
+ When the +row.tmpl+ is rendered each time, its imported +user+ is set to whatever the +user+ in the container is, in this case the next object in +assigned_users+.
297
+
298
+ You can import values into deeply-nested templates, provided each container imports it as well.
299
+
300
+
301
+ === publish/subscribe
302
+
303
+ Often you'll want to set up a generic layout template to establish a global look-and-feel, and then modify it based on the content of an inner template.
304
+
305
+ ==== Look and feel template (layout.tmpl)
306
+ <!DOCTYPE HTML>
307
+ <html lang="en">
308
+ <head>
309
+ <title><?subscribe title || Untitled ?></title>
310
+ <link rel="stylesheet" href="/css/base.css" type="text/css" media="screen"
311
+ title="Base Stylesheet" charset="utf-8" />
312
+ <?subscribe stylesheets ?>
313
+
314
+ <script defer="defer" src="/js/jquery-1.4.2.min.js"
315
+ type="text/javascript" charset="utf-8"></script>
316
+ <?subscribe scripts ?>
317
+ </head>
318
+
319
+ <body><?attr body ?></body>
320
+ </html>
321
+
322
+
323
+ ==== A content template (content.tmpl)
324
+ <?publish title ?>I make stuff up<?end publish?>
325
+
326
+ <?publish stylesheets ?>
327
+ <link rel="stylesheet" href="/css/content.css" type="text/css" media="screen"
328
+ title="Content Style Overrides" charset="utf-8" />
329
+ <?end publish?>
330
+
331
+ <?publish scripts ?>
332
+ <script defer="defer" src="/js/content.js" type="text/javascript" charset="utf-8"></script>
333
+ <?end publish?>
334
+
335
+ <div>Hi, there.</div>
336
+
337
+ ==== Template setup
338
+
339
+ layout = Inversion::Template.load( 'layout.tmpl' )
340
+ content = Inversion::Template.load( 'content.tmpl' )
341
+
342
+ layout.body = content
343
+
344
+ puts layout.render
345
+
346
+ +subscribe+ renders to an empty string if there is no matching +publish+, or to the value of a default if supplied (as in the HTML title example above.)
347
+ In this fashion, you can dynamically switch out different content pages, with each having the ability to optionally override various HTML elements.
348
+
349
+ === include
350
+
351
+ The +include+ tag allows inclusion of other template files from within a template. This supports separation of a template into several reusable components. The included template becomes a part of the including template, along with any defaults, attributes and configuration.
352
+
353
+ ==== Include setup
354
+
355
+ email = Inversion::Template.load( 'email.tmpl' )
356
+
357
+ email.greeting = "Kudos"
358
+ email.company = Company[ :spime_thorpe ]
359
+ email.user = User[ :jrandom ]
360
+
361
+ puts main.render
362
+
363
+ ==== Including template (email.tmpl)
364
+
365
+ Subject: Great news, everybody!
366
+ From: <?attr company.email ?>
367
+ To: <?attr user.email ?>
368
+
369
+ <?attr greeting ?>, <?attr user.first_name ?>!
370
+
371
+ We are excited to inform you that you have been selected to participate
372
+ in a challenging and exciting career displacement opportunity!
373
+
374
+ Please attend the mandatory Man Overboard (tm) session we have scheduled
375
+ for you at 8:45AM on Thursday in the Sunshine Room. Light refreshments
376
+ and computer-aided aptitude testing will be provided.
377
+
378
+ <?include signature.tmpl ?>
379
+
380
+ ==== Included template (signature.tmpl)
381
+
382
+ Sincerely,
383
+ Your Friends at <?attr company.name ?>!
384
+
385
+ ==== The rendered output
386
+
387
+ Subject: Great news, everybody!
388
+ From: "Spime-Thorpe, Inc." <salesteam2@spime-thorpe.com>
389
+ To: "James Random" <jrandom@compusa.com>
390
+
391
+ Kudos, James!
392
+
393
+ We are excited to inform you that you have been selected to participate
394
+ in a challenging and exciting career displacement opportunity!
395
+
396
+ Please attend the mandatory Man Overboard (tm) session we have scheduled
397
+ for you at 8:45AM on Thursday in the Sunshine Room. Light refreshments
398
+ and computer-aided aptitude testing will be provided.
399
+
400
+ Sincerely,
401
+ Your Friends at Spime Thorpe!
402
+
403
+
404
+ == Flow Control
405
+
406
+ The following tags are used to alter the flow of rendering from within templates.
407
+
408
+
409
+ === for
410
+
411
+ The +for+ tag iterates over the objects in a collection, rendering its
412
+ template section once for each iteration. Its attribute can be set to anything
413
+ that responds to @#each@. The iteration variable(s) are scoped to the block,
414
+ and temporarily override any template attributes of the same name.
415
+
416
+ ==== 'For' tag setup
417
+
418
+ overhead_list = Inversion::Template.load( 'employee_list.tmpl' )
419
+ overhead_list.users = User.
420
+ filter { start_date < 6.months.ago }.
421
+ filter { department = 'Information Technology' }
422
+
423
+ puts overhead_list.render
424
+
425
+ The +for+ tag's iteration works just like Ruby's +for+; if the enumerated
426
+ value has more than one value, you can give a list of iteration variables to
427
+ be assigned to.
428
+
429
+ ==== Employee list using 'for'
430
+
431
+ <table>
432
+ <thead>...</thead>
433
+ <tbody>
434
+ <?for user, i in users.each_with_index ?>
435
+ <tr class="[?if i.even? ?]even[?else?]odd[?end if?]-row">
436
+ <td><?attr user.first_name ?></td>
437
+ <td><?attr user.last_name ?></td>
438
+ <td><?attr user.title ?></td>
439
+ <td><?attr user.start_date ?></td>
440
+ <td><?attr user.salary ?></td>
441
+ </tr>
442
+ <?end for ?>
443
+ </tbody>
444
+ </table>
445
+
446
+ The example above uses a Ruby enumerator for the +#each_with_index+ method to set the class of the row to +'even-row'+ or +'odd-row'+.
447
+
448
+ This works with the keys and values of Hashes, too:
449
+
450
+ ==== Display hash of notes keyed by author using 'for'
451
+
452
+ <?for user, content in user.notes ?>
453
+ <section class="note">
454
+ <header>
455
+ Note by <?call user.username ?>
456
+ </header>
457
+ <p><?escape content ?></p>
458
+ </section>
459
+
460
+ <?end for ?>
461
+
462
+ Note that you can also use Ruby's "external iterator" syntax to iterate, too:
463
+
464
+ ==== Iterate over each byte of a string with an index using 'for'
465
+
466
+ <section class="hexdump">
467
+ <?for byte, index in frame.header.each_byte.with_index ?>
468
+ <?if index.modulo(8).zero? ?>
469
+ <?if index.nonzero? ?>
470
+ </span><br />
471
+ <?end if ?>
472
+ <span class="row"><?attr "0x%08x" % index ?>:
473
+ <?end if ?>
474
+ &nbsp;<code><?attr "0x%02x" % byte ?></code>
475
+ <?end for ?>
476
+ </section>
477
+
478
+
479
+
480
+ === if/elsif/else
481
+
482
+ The +if+ tag can be used to conditionally render a section of the template based on the value of an attribute or the value of a method called on it.
483
+
484
+ ==== Conditional block
485
+
486
+ <?if user.has_stock_options? ?>
487
+ You will have 21 days to exercise your stock options.
488
+ <?else ?>
489
+ You have a week to optionally take home a handful of supplies from the
490
+ office cabinet.
491
+ <?end if ?>
492
+
493
+
494
+ === unless
495
+
496
+ Unless is like the +if+ tag, but with inverted logic. Note that an +unless+ can have an +else+ tag, but cannot have any +elsif+ tags within it.
497
+
498
+
499
+ === yield
500
+
501
+ The +yield+ tag is used to defer rendering of some part of the template to the code that is calling render[rdoc-ref:Inversion::Template#render] on it. If a block is passed to +#render+, then the +yield+ tag will call it with the Inversion::RenderState object that is currently in effect, and will render the return value in its place.
502
+
503
+ ==== Using 'yield' to defer an expensive database lookup (report.tmpl)
504
+
505
+ <?if extra_details_enabled ?>
506
+ <?yield ?>
507
+ <?end if ?>
508
+
509
+ report = Inversion::Template.load( 'report.tmpl' )
510
+ report.extra_details_enabled = true if $DEBUG
511
+ puts report.render do
512
+ report_table = Inversion::Template.load( 'table.tmpl' )
513
+ report_table.rows = an_expensive_database_query()
514
+ report_table
515
+ end
516
+
517
+ This will insert the +report_table+ template in place of the yield, but only if $DEBUG is true.
518
+
519
+
520
+ == Troubleshooting/Introspection
521
+
522
+ === pp
523
+
524
+ The +pp+ tag uses the +PP+ library to output an escaped representation of its argument.
525
+
526
+ ==== Creating an object to inspect
527
+
528
+ content = Inversion::Template.load( 'content.tmpl' )
529
+ content.file = File.stat( '/tmp/example.txt' )
530
+
531
+ puts content.render
532
+
533
+ ==== Inspecting an object from within a template (content.tmpl)
534
+
535
+ <div class="debugging">
536
+ The file's stat attributes:
537
+ <?pp file ?>
538
+ </div>
539
+
540
+ The output is escaped according to the current setting of the {:escape_format}[rdoc-ref:Templates@Template+Options] option.
541
+
542
+ ==== The rendered result
543
+
544
+ <div class="debugging">
545
+ The file's stat attributes:
546
+ #&lt;File::Stat
547
+ dev=0xe000004,
548
+ ino=3064556,
549
+ mode=0100644 (file rw-r--r--),
550
+ nlink=1,
551
+ uid=501 (mahlon),
552
+ gid=0 (wheel),
553
+ rdev=0x0 (0, 0),
554
+ size=0,
555
+ blksize=4096,
556
+ blocks=0,
557
+ atime=2011-08-12 08:43:15 -0700 (1313163795),
558
+ mtime=2011-08-12 08:43:15 -0700 (1313163795),
559
+ ctime=2011-08-12 08:43:15 -0700 (1313163795)&gt;</div>
560
+ </div>
561
+