inversion 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Examples.md +134 -0
  4. data/GettingStarted.md +50 -0
  5. data/Guide.md +14 -0
  6. data/{History.rdoc → History.md} +52 -43
  7. data/{README.rdoc → README.md} +43 -34
  8. data/Tags.md +555 -0
  9. data/bin/inversion +4 -8
  10. data/lib/inversion/cli/api.rb +75 -0
  11. data/lib/inversion/cli/tagtokens.rb +34 -0
  12. data/lib/inversion/cli/tree.rb +70 -0
  13. data/lib/inversion/cli.rb +406 -0
  14. data/lib/inversion/exceptions.rb +0 -1
  15. data/lib/inversion/mixins.rb +10 -11
  16. data/lib/inversion/parser.rb +5 -6
  17. data/lib/inversion/refinements.rb +18 -0
  18. data/lib/inversion/renderstate.rb +24 -25
  19. data/lib/inversion/sinatra.rb +0 -1
  20. data/lib/inversion/template/attrtag.rb +7 -5
  21. data/lib/inversion/template/begintag.rb +0 -1
  22. data/lib/inversion/template/calltag.rb +0 -1
  23. data/lib/inversion/template/codetag.rb +37 -28
  24. data/lib/inversion/template/commenttag.rb +0 -1
  25. data/lib/inversion/template/configtag.rb +3 -4
  26. data/lib/inversion/template/containertag.rb +1 -2
  27. data/lib/inversion/template/defaulttag.rb +1 -2
  28. data/lib/inversion/template/elsetag.rb +0 -1
  29. data/lib/inversion/template/elsiftag.rb +0 -1
  30. data/lib/inversion/template/endtag.rb +2 -3
  31. data/lib/inversion/template/escapetag.rb +1 -2
  32. data/lib/inversion/template/fortag.rb +2 -3
  33. data/lib/inversion/template/fragmenttag.rb +1 -2
  34. data/lib/inversion/template/iftag.rb +3 -1
  35. data/lib/inversion/template/importtag.rb +2 -3
  36. data/lib/inversion/template/includetag.rb +1 -2
  37. data/lib/inversion/template/node.rb +4 -5
  38. data/lib/inversion/template/pptag.rb +1 -2
  39. data/lib/inversion/template/publishtag.rb +2 -3
  40. data/lib/inversion/template/rescuetag.rb +1 -2
  41. data/lib/inversion/template/subscribetag.rb +3 -4
  42. data/lib/inversion/template/tag.rb +3 -4
  43. data/lib/inversion/template/textnode.rb +1 -2
  44. data/lib/inversion/template/timedeltatag.rb +1 -2
  45. data/lib/inversion/template/unlesstag.rb +0 -1
  46. data/lib/inversion/template/uriencodetag.rb +1 -2
  47. data/lib/inversion/template/yieldtag.rb +0 -1
  48. data/lib/inversion/template.rb +18 -22
  49. data/lib/inversion/tilt.rb +1 -2
  50. data/lib/inversion.rb +2 -3
  51. data/spec/helpers.rb +19 -13
  52. data/spec/inversion/mixins_spec.rb +1 -1
  53. data/spec/inversion/monkeypatches_spec.rb +1 -1
  54. data/spec/inversion/parser_spec.rb +1 -1
  55. data/spec/inversion/renderstate_spec.rb +1 -1
  56. data/spec/inversion/sinatra_spec.rb +1 -1
  57. data/spec/inversion/template/attrtag_spec.rb +1 -1
  58. data/spec/inversion/template/begintag_spec.rb +1 -1
  59. data/spec/inversion/template/calltag_spec.rb +1 -1
  60. data/spec/inversion/template/codetag_spec.rb +24 -1
  61. data/spec/inversion/template/commenttag_spec.rb +1 -1
  62. data/spec/inversion/template/configtag_spec.rb +1 -1
  63. data/spec/inversion/template/containertag_spec.rb +1 -1
  64. data/spec/inversion/template/defaulttag_spec.rb +1 -1
  65. data/spec/inversion/template/elsetag_spec.rb +1 -1
  66. data/spec/inversion/template/elsiftag_spec.rb +1 -1
  67. data/spec/inversion/template/endtag_spec.rb +1 -1
  68. data/spec/inversion/template/escapetag_spec.rb +1 -1
  69. data/spec/inversion/template/fortag_spec.rb +1 -1
  70. data/spec/inversion/template/fragmenttag_spec.rb +1 -1
  71. data/spec/inversion/template/iftag_spec.rb +1 -1
  72. data/spec/inversion/template/importtag_spec.rb +1 -1
  73. data/spec/inversion/template/includetag_spec.rb +1 -1
  74. data/spec/inversion/template/node_spec.rb +1 -1
  75. data/spec/inversion/template/pptag_spec.rb +1 -1
  76. data/spec/inversion/template/publishtag_spec.rb +1 -1
  77. data/spec/inversion/template/rescuetag_spec.rb +1 -1
  78. data/spec/inversion/template/subscribetag_spec.rb +1 -1
  79. data/spec/inversion/template/tag_spec.rb +1 -1
  80. data/spec/inversion/template/textnode_spec.rb +1 -1
  81. data/spec/inversion/template/timedeltatag_spec.rb +1 -1
  82. data/spec/inversion/template/unlesstag_spec.rb +1 -1
  83. data/spec/inversion/template/uriencodetag_spec.rb +1 -1
  84. data/spec/inversion/template/yieldtag_spec.rb +1 -1
  85. data/spec/inversion/template_spec.rb +1 -1
  86. data/spec/inversion/tilt_spec.rb +1 -1
  87. data/spec/inversion_spec.rb +1 -1
  88. data.tar.gz.sig +0 -0
  89. metadata +59 -45
  90. metadata.gz.sig +0 -0
  91. data/Examples.rdoc +0 -134
  92. data/GettingStarted.rdoc +0 -44
  93. data/Guide.rdoc +0 -47
  94. data/Manifest.txt +0 -86
  95. data/Rakefile +0 -7
  96. data/Tags.rdoc +0 -560
  97. data/lib/inversion/command.rb +0 -278
  98. data/lib/inversion/monkeypatches.rb +0 -21
data/Tags.md ADDED
@@ -0,0 +1,555 @@
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](#label-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: `<?tagname tagdata ?>`
11
+ - or the same thing, but with square brackets instead: `[?tagname tagdata ?]`
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
+ <a href="[?call article.permalink ?]">Permalink</a>
16
+
17
+ You can mix tag forms in a single document.
18
+
19
+
20
+ ## Placeholder Tags
21
+
22
+ 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.
23
+
24
+
25
+ ### `attr`
26
+
27
+ 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:
28
+
29
+ Title: <?attr title ?>
30
+
31
+ Calling the template object's `#title=` method will inject the stringified value into that part of the output when it's rendered, e.g.,
32
+
33
+ template.title = "How to Breed Kangaroos for Milk and Meat"
34
+ template.render
35
+ # => "Title: How to Breed Kangaroos for Milk and Meat"
36
+
37
+ The rendered values of an `attr` tag can also be the result of calling methods on the attr value:
38
+
39
+ ISBN: <?attr book.isbn ?>
40
+
41
+ Attributes can be sprintf formatted using Ruby's String#% method:
42
+
43
+ Book price: <?attr "%0.2f" % book.price ?>
44
+
45
+ Attributes can also contain other template objects, which allows templates to be nested within each other easily.
46
+
47
+ layout = Inversion::Template.load( 'layout.tmpl' )
48
+ content = Inversion::Template.load( 'content.tmpl' )
49
+ content.caption = "Your kids will love their new Kangaroo family!"
50
+ layout.body = content
51
+ layout.render
52
+
53
+
54
+ ### `call`
55
+
56
+ `call` is just an alias for `attr`. Use whichever strikes your fancy.
57
+
58
+
59
+ ### `escape`
60
+
61
+ `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.
62
+
63
+ <p>Company name: <?escape company.name ?></p>
64
+
65
+ If the company was `"AT&T"`, the output would look like:
66
+
67
+ <p>Company name: AT&amp;T</p>
68
+
69
+
70
+ ### `uriencode`
71
+
72
+ The `urlencode` tag is another `attr`-like tag, but this one does URI encoding:
73
+
74
+ <nav>Edit <a href="/profile?name=[?uriencode person.name ?]">your profile</a></nav>
75
+
76
+
77
+ ## Special Placeholders
78
+
79
+ ### `timedelta`
80
+
81
+ If you need to automatically generate a human-readable description of the interval between two times, you can use the `timedelta` tag:
82
+
83
+ <article class="blogentry">
84
+ <header>
85
+ <p>Posted: <?timedelta entry.date_posted ?>.</p>
86
+ </header>
87
+ ...
88
+ </article>
89
+
90
+ The tag supports any object which responds to the `#to_time` method, so standard `Time`, `Date`, and `DateTime` objects all work.
91
+
92
+ Dates are compared against the current time, and render to approximate descriptions of the interval, e.g.,
93
+
94
+ * 4 days ago
95
+ * about an hour from now
96
+ * 6 weeks ago
97
+ * less than a minute from now
98
+
99
+
100
+ ## Inter-template Tags
101
+
102
+ These tags operate on nested templates, allowing you to selectively use or send attributes or content from other templates.
103
+
104
+
105
+ ### `import`
106
+
107
+ 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:
108
+
109
+ <!-- layout.tmpl -->
110
+ Logged in as: <?attr request.authenticated_user ?>
111
+ <?attr body ?>
112
+
113
+ <!-- body.tmpl -->
114
+ <?import request ?>
115
+ <p>You can check your balance using <a href="[?call request.path_info ?]/accounts">the
116
+ accounts tool</a>.</p>
117
+
118
+ 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:
119
+
120
+ layout = Inversion::Template.load( 'layout.tmpl' )
121
+ body = Inversion::Template.load( 'body.tmpl' )
122
+
123
+ layout.body = body
124
+ layout.request = request
125
+
126
+ puts layout.render
127
+
128
+ Without the use of `import`, you'd need to similarly set the request attribute on the body template.
129
+
130
+ The imported attribute's value is determined at render time, so you can also use it to import values from an iteration.
131
+
132
+ <!-- Container template (table.tmpl)" -->
133
+ <table>
134
+ <thead>...</thead>
135
+ <tbody>
136
+ <?for user in assigned_users ?>
137
+ <?attr row ?>
138
+ <?end for ?>
139
+ </tbody>
140
+ </table>
141
+ <?end?>
142
+
143
+ <!-- Content template (row.tmpl)" -->
144
+ <?import user ?>
145
+ <tr>
146
+ <th>Username:</th><td><?escape user.username ?></td>
147
+ <th>UID:</th><td><?escape user.uid ?></td>
148
+ <th>GID:</th><td><?escape user.gid ?></td>
149
+ </tr>
150
+ <?end?>
151
+
152
+ and the code:
153
+
154
+ usertable = Inversion::Template.load( 'table.tmpl' )
155
+ userrow = Inversion::Template.load( 'row.tmpl' )
156
+
157
+ usertable.row = userrow
158
+ usertable.assigned_users = User.assigned.all
159
+
160
+ puts usertable.render
161
+
162
+ 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`.
163
+
164
+ You can import values into deeply-nested templates, provided each container imports it as well.
165
+
166
+
167
+ ### `publish`/`subscribe`
168
+
169
+ 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.
170
+
171
+ #### Look and feel template (`layout.tmpl`)
172
+
173
+ <!DOCTYPE HTML>
174
+ <html lang="en">
175
+ <head>
176
+ <title><?subscribe title || Untitled ?></title>
177
+ <link rel="stylesheet" href="/css/base.css" type="text/css" media="screen"
178
+ title="Base Stylesheet" charset="utf-8" />
179
+ <?subscribe stylesheets ?>
180
+
181
+ <script defer="defer" src="/js/jquery-1.4.2.min.js"
182
+ type="text/javascript" charset="utf-8"></script>
183
+ <?subscribe scripts ?>
184
+ </head>
185
+
186
+ <body><?attr body ?></body>
187
+ </html>
188
+
189
+
190
+ #### A content template (`content.tmpl`)
191
+
192
+ <?publish title ?>I make stuff up<?end publish?>
193
+
194
+ <?publish stylesheets ?>
195
+ <link rel="stylesheet" href="/css/content.css" type="text/css" media="screen"
196
+ title="Content Style Overrides" charset="utf-8" />
197
+ <?end publish?>
198
+
199
+ <?publish scripts ?>
200
+ <script defer="defer" src="/js/content.js" type="text/javascript" charset="utf-8"></script>
201
+ <?end publish?>
202
+
203
+ <div>Hi, there.</div>
204
+
205
+ #### Template setup
206
+
207
+ layout = Inversion::Template.load( 'layout.tmpl' )
208
+ content = Inversion::Template.load( 'content.tmpl' )
209
+
210
+ layout.body = content
211
+
212
+ puts layout.render
213
+
214
+ `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.)
215
+ In this fashion, you can dynamically switch out different content pages, with each having the ability to optionally override various HTML elements.
216
+
217
+ ### `include`
218
+
219
+ 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.
220
+
221
+ #### `include` setup
222
+
223
+ email = Inversion::Template.load( 'email.tmpl' )
224
+
225
+ email.greeting = "Kudos"
226
+ email.company = Company[ :spime_thorpe ]
227
+ email.user = User[ :jrandom ]
228
+
229
+ puts main.render
230
+
231
+ #### Including template (`email.tmpl`)
232
+
233
+ Subject: Great news, everybody!
234
+ From: <?attr company.email ?>
235
+ To: <?attr user.email ?>
236
+
237
+ <?attr greeting ?>, <?attr user.first_name ?>!
238
+
239
+ We are excited to inform you that you have been selected to participate
240
+ in a challenging and exciting career displacement opportunity!
241
+
242
+ Please attend the mandatory Man Overboard (tm) session we have scheduled
243
+ for you at 8:45AM on Thursday in the Sunshine Room. Light refreshments
244
+ and computer-aided aptitude testing will be provided.
245
+
246
+ <?include signature.tmpl ?>
247
+
248
+ #### Included template (`signature.tmpl`)
249
+
250
+ Sincerely,
251
+ Your Friends at <?attr company.name ?>!
252
+
253
+ #### The rendered output
254
+
255
+ Subject: Great news, everybody!
256
+ From: "Spime-Thorpe, Inc." <salesteam2@spime-thorpe.com>
257
+ To: "James Random" <jrandom@compusa.com>
258
+
259
+ Kudos, James!
260
+
261
+ We are excited to inform you that you have been selected to participate
262
+ in a challenging and exciting career displacement opportunity!
263
+
264
+ Please attend the mandatory Man Overboard (tm) session we have scheduled
265
+ for you at 8:45AM on Thursday in the Sunshine Room. Light refreshments
266
+ and computer-aided aptitude testing will be provided.
267
+
268
+ Sincerely,
269
+ Your Friends at Spime Thorpe!
270
+
271
+
272
+ ### `fragment`
273
+
274
+ A `fragment` tag also sets an attribute from within the template, but under the
275
+ scope of the global template itself. A fragment can use other Inversion tags,
276
+ and the attribute is both usable elsewhere in the template, and accessible from
277
+ calling code after rendering.
278
+
279
+ template = Inversion::Template.new <<-TMPL
280
+ <?fragment subject ?>Your order status (Order #<?call order.number ?>)<?end ?>
281
+
282
+ Dear <?call order.customer.name ?>,
283
+
284
+ Your recent order was modified by our Order Fulfillment Team.
285
+
286
+ After careful deliberation, it was decided that no one should have need for that many hot dogs
287
+ with overnight shipping. Frankly, we're more than a little concerned for your health.
288
+
289
+ Sincerely,
290
+ Rowe's Meat Emporium
291
+ (Buy! Sell! Consignment!)
292
+ TMPL
293
+
294
+ template.order = order
295
+ template.render
296
+
297
+ template.fragments[ :subject ] #=> "Your order status (Order #3492)"
298
+
299
+
300
+ ## Flow Control
301
+
302
+ The following tags are used to alter the flow of rendering from within templates.
303
+
304
+
305
+ ### `for`
306
+
307
+ The `for` tag iterates over the objects in a collection, rendering its
308
+ template section once for each iteration. Its attribute can be set to anything
309
+ that responds to @#each@. The iteration variable(s) are scoped to the block,
310
+ and temporarily override any template attributes of the same name.
311
+
312
+ #### `for` tag setup
313
+
314
+ overhead_list = Inversion::Template.load( 'employee_list.tmpl' )
315
+ overhead_list.users = User.
316
+ filter { start_date < 6.months.ago }.
317
+ filter { department = 'Information Technology' }
318
+
319
+ puts overhead_list.render
320
+
321
+ The `for` tag's iteration works just like Ruby's `for`; if the enumerated
322
+ value has more than one value, you can give a list of iteration variables to
323
+ be assigned to.
324
+
325
+ #### Employee list using `for`
326
+
327
+ <table>
328
+ <thead>...</thead>
329
+ <tbody>
330
+ <?for user, i in users.each_with_index ?>
331
+ <tr class="[?if i.even? ?]even[?else?]odd[?end if?]-row">
332
+ <td><?attr user.first_name ?></td>
333
+ <td><?attr user.last_name ?></td>
334
+ <td><?attr user.title ?></td>
335
+ <td><?attr user.start_date ?></td>
336
+ <td><?attr user.salary ?></td>
337
+ </tr>
338
+ <?end for ?>
339
+ </tbody>
340
+ </table>
341
+
342
+ 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'`.
343
+
344
+ This works with the keys and values of Hashes, too:
345
+
346
+ #### Display hash of notes keyed by author using `for`
347
+
348
+ <?for user, content in user.notes ?>
349
+ <section class="note">
350
+ <header>
351
+ Note by <?call user.username ?>
352
+ </header>
353
+ <p><?escape content ?></p>
354
+ </section>
355
+
356
+ <?end for ?>
357
+
358
+ Note that you can also use Ruby's "external iterator" syntax to iterate, too:
359
+
360
+ #### Iterate over each byte of a string with an index using `for`
361
+
362
+ <section class="hexdump">
363
+ <?for byte, index in frame.header.each_byte.with_index ?>
364
+ <?if index.modulo(8).zero? ?>
365
+ <?if index.nonzero? ?>
366
+ </span><br />
367
+ <?end if ?>
368
+ <span class="row"><?attr "0x%08x" % index ?>:
369
+ <?end if ?>
370
+ &nbsp;`<?attr "0x%02x" % byte ?>`
371
+ <?end for ?>
372
+ </section>
373
+
374
+
375
+
376
+ ### `if`/`elsif`/`else`
377
+
378
+ 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.
379
+
380
+ #### Conditional block
381
+
382
+ <?if user.has_stock_options? ?>
383
+ You will have 21 days to exercise your stock options.
384
+ <?else ?>
385
+ You have a week to optionally take home a handful of supplies from the
386
+ office cabinet.
387
+ <?end if ?>
388
+
389
+
390
+ ### `unless`
391
+
392
+ 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.
393
+
394
+
395
+ ### `yield`
396
+
397
+ 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.
398
+
399
+ #### Using `yield` to defer an expensive database lookup (`report.tmpl`)
400
+
401
+ <?if extra_details_enabled ?>
402
+ <?yield ?>
403
+ <?end if ?>
404
+
405
+ report = Inversion::Template.load( 'report.tmpl' )
406
+ report.extra_details_enabled = true if $DEBUG
407
+ puts report.render do
408
+ report_table = Inversion::Template.load( 'table.tmpl' )
409
+ report_table.rows = an_expensive_database_query()
410
+ report_table
411
+ end
412
+
413
+ This will insert the `report_table` template in place of the yield, but only if $DEBUG is true.
414
+
415
+
416
+ ### `begin`/`rescue`
417
+
418
+ These tags work as you'd expect from their ruby counterparts.
419
+
420
+ The `begin` section of the template to be rendered only if no exceptions are raised while it's
421
+ being rendered. If an exception is raised, it is checked against any `rescue` sections, and the
422
+ first with a matching exception is rendered instead. If no `rescue` block is found, the exception
423
+ is handled by the configured exception behavior for the template (see Inversion::Template@Template+Options).
424
+
425
+ <?begin ?><?call employees.length ?><?end?>
426
+
427
+ <?begin ?>
428
+ <?for employee in employees.all ?>
429
+ <?attr employee.name ?> --> <?attr employee.title ?>
430
+ <?end for?>
431
+ <?rescue DatabaseError => err ?>
432
+ Oh no!! I can't talk to the database for some reason. The
433
+ error was as follows:
434
+ <pre>
435
+ <?attr err.message ?>
436
+ </pre>
437
+ <?end?>
438
+
439
+
440
+ ## Control Tags
441
+
442
+ There are a few tags that can be used to set values in the templating system itself.
443
+
444
+ ### `config`
445
+
446
+ The `config` tag can be used to override template options[Templates@Template+Options]
447
+ on a per-template basis. It allows for convenient, inline settings from
448
+ within a template rather than from the code in which the template is
449
+ loaded.
450
+
451
+ For example, if you want to enable debugging comments on a single template:
452
+
453
+ <?config debugging_comments: true ?>
454
+
455
+ Multiple template options can be set simultaneously by using a YAML hash:
456
+
457
+ <?config
458
+ on_render_error: propagate
459
+ debugging_comments: true
460
+ comment_start: /*
461
+ comment_end: */
462
+ ?>
463
+
464
+ Note that this also allows you to set multiple options on a single line, if you wrap them in braces:
465
+
466
+ <?config { comment_start: "/*", comment_end: "*/" } ?>
467
+
468
+
469
+ ### `default`
470
+
471
+ The `default` tag sets an attribute from within the template,
472
+ and this value is used if the attribute is set to nil or otherwise unset.
473
+
474
+ template = Inversion::Template.new <<-TMPL
475
+ <?default adjective to "cruel" ?>
476
+ <?default noun to "world" ?>
477
+ Goodbye, <?attr adjective ?> <?attr noun ?>!
478
+ TMPL
479
+
480
+ template.render
481
+
482
+ template.adjective = "delicious"
483
+ template.render
484
+
485
+ template.adjective = nil
486
+ template.noun = "banana"
487
+ template.render
488
+
489
+ Would produce the output:
490
+
491
+ Goodbye, cruel world!
492
+ Goodbye, delicious world!
493
+ Goodbye, cruel banana!
494
+
495
+
496
+
497
+ ## Troubleshooting/Introspection
498
+
499
+ ### `pp`
500
+
501
+ The `pp` tag uses the `PP` library to output an escaped representation of its argument.
502
+
503
+ #### Creating an object to inspect
504
+
505
+ content = Inversion::Template.load( 'content.tmpl' )
506
+ content.file = File.stat( '/tmp/example.txt' )
507
+
508
+ puts content.render
509
+
510
+ #### Inspecting an object from within a template (`content.tmpl`)
511
+
512
+ <div class="debugging">
513
+ The file's stat attributes:
514
+ <?pp file ?>
515
+ </div>
516
+
517
+ The output is escaped according to the current setting of the [:escape_format](rdoc-ref:Templates@Template+Options) option.
518
+
519
+ #### The rendered result
520
+
521
+ <div class="debugging">
522
+ The file's stat attributes:
523
+ #&lt;File::Stat
524
+ dev=0xe000004,
525
+ ino=3064556,
526
+ mode=0100644 (file rw-r--r--),
527
+ nlink=1,
528
+ uid=501 (mahlon),
529
+ gid=0 (wheel),
530
+ rdev=0x0 (0, 0),
531
+ size=0,
532
+ blksize=4096,
533
+ blocks=0,
534
+ atime=2011-08-12 08:43:15 -0700 (1313163795),
535
+ mtime=2011-08-12 08:43:15 -0700 (1313163795),
536
+ ctime=2011-08-12 08:43:15 -0700 (1313163795)&gt;</div>
537
+ </div>
538
+
539
+
540
+ ## Custom Tags
541
+
542
+ We have a lot of documentation work to do for this still, but the basics are:
543
+
544
+ * Create a file with the path `lib/inversion/template/«yourtagnametag».rb/code>
545
+ * Name your class Inversion::Template::«yourtagname.capitalize»Tag
546
+ * Subclass one of the abstract template tag types:
547
+ * If your tag body will contain code, subclass Inversion::Template::CodeTag
548
+ * Otherwise, subclass Inversion::Template::Tag
549
+ * If your tag is a container, include Inversion::Template::ContainerTag
550
+ * Read the documentation for the tag type you're subclassing for the methods
551
+ you're required to implement.
552
+
553
+ Unfortunately, the tag superclasses aren't currently documented very well, so
554
+ the best way to accomplish what you want is to find an existing tag that does
555
+ something similar and look at how it does it.
data/bin/inversion CHANGED
@@ -1,12 +1,8 @@
1
1
  # -*- ruby -*-
2
- # frozen_string_literal: true
3
2
  # vim: set noet nosta sw=4 ts=4 :
4
3
 
5
- require 'inversion'
6
- require 'inversion/command'
7
-
8
- Inversion::Command.run( ARGV )
9
-
10
-
11
-
4
+ require 'loggability'
5
+ Loggability.level = :fatal
12
6
 
7
+ require 'inversion/cli'
8
+ exit Inversion::CLI.run( ARGV )
@@ -0,0 +1,75 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'inversion'
4
+ require 'inversion/cli'
5
+
6
+
7
+ # Api command
8
+ module Inversion::CLI::ApiCommand
9
+ extend Inversion::CLI::Subcommand
10
+
11
+
12
+ desc "Dump the Ruby API of the given TEMPLATEs"
13
+ long_desc %{
14
+ Load the given TEMPLATE and dump the out the Ruby API of the resulting object.
15
+ }
16
+ arg :TEMPLATE, :multiple
17
+ command :api do |api|
18
+
19
+ api.action do |globals, options, args|
20
+ args.each do |path|
21
+
22
+ template = self.load_template( path ) or exit_now!('Template failed to load.')
23
+
24
+ self.output_blank_line
25
+ self.output_template_header( template )
26
+ self.describe_template_api( template )
27
+ self.describe_publications( template )
28
+ self.describe_subscriptions( template )
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+
35
+ ###############
36
+ module_function
37
+ ###############
38
+
39
+
40
+ ### Output a description of the `template`'s attributes, subscriptions, etc.
41
+ def describe_template_api( template )
42
+ attrs = template.attributes.keys.map( &:to_s )
43
+ return if attrs.empty?
44
+
45
+ self.output_subheader "%d Attribute/s" % [ attrs.length ]
46
+ self.display_list( attrs )
47
+ self.output_blank_line
48
+ end
49
+
50
+
51
+ ### Output a list of sections the template publishes.
52
+ def describe_publications( template )
53
+ ptags = template.node_tree.find_all {|node| node.is_a?(Inversion::Template::PublishTag) }
54
+ return if ptags.empty?
55
+
56
+ pubnames = ptags.map( &:key ).map( &:to_s ).uniq.sort
57
+ self.output_subheader "%d Publication/s" % [ pubnames.length ]
58
+ self.display_list( pubnames )
59
+ self.output_blank_line
60
+ end
61
+
62
+
63
+ ### Output a list of sections the template subscribes to.
64
+ def describe_subscriptions( template )
65
+ stags = template.node_tree.find_all {|node| node.is_a?(Inversion::Template::SubscribeTag) }
66
+ return if stags.empty?
67
+
68
+ subnames = stags.map( &:key ).map( &:to_s ).uniq.sort
69
+ self.output_subheader "%d Subscription/s" % [ subnames.length ]
70
+ self.display_list( subnames )
71
+ self.output_blank_line
72
+ end
73
+
74
+ end # module Inversion::CLI::ApiCommand
75
+
@@ -0,0 +1,34 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'inversion'
4
+ require 'inversion/cli'
5
+
6
+
7
+ # Tagtokens command
8
+ module Inversion::CLI::TagTokensCommand
9
+ extend Inversion::CLI::Subcommand
10
+
11
+
12
+ desc "Dump a token phrase for the given STATEMENT"
13
+ long_desc %{
14
+ Parse the given STATEMENT as Ruby and dump the resulting lexical
15
+ tokens. This is useful when creating new tags.
16
+ }
17
+ arg :STATEMENT
18
+ command :tagtokens do |tagtokens|
19
+
20
+ tagtokens.action do |globals, options, args|
21
+ statement = args.join(' ')
22
+
23
+ require 'ripper'
24
+ tokens = Ripper.lex( statement ).collect do |(pos, tok, text)|
25
+ "%s<%p>" % [ tok.to_s.sub(/^on_/,''), text ]
26
+ end.join(' ')
27
+
28
+ self.prompt.say( tokens )
29
+ end
30
+
31
+ end
32
+
33
+ end # module Inversion::CLI::TagTokensCommand
34
+