ruty 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,450 @@
1
+ # = Ruty -- Ruby Templating
2
+ #
3
+ # Author:: Armin Ronacher
4
+ #
5
+ # Copyright (c) 2006 by Armin Ronacher
6
+ #
7
+ # You can redistribute it and/or modify it under the terms of the BSD license.
8
+ #
9
+ #
10
+ # Ruty is a template engine heavily inspired by the Django and Jinja Template
11
+ # Engine. It supports template inheritance, template inclusion and most of the
12
+ # tags supported by Jinja/Django.
13
+ #
14
+ # Quickstart
15
+ # ==========
16
+ #
17
+ # The simplest way to load templates is by using the Ruty::Template class:
18
+ #
19
+ # require 'ruty'
20
+ # t = Ruty::Template.new('Hello {{ username }}!')
21
+ # puts r.render(:username => 'John Doe')
22
+ #
23
+ # Outputs:
24
+ # Hello John Doe!
25
+ #
26
+ # By just using the Ruty::Template class you can't use the powerful template
27
+ # inheritance system of ruty. To have that working you must load templates
28
+ # via a loader instance. Ruty ships two loaders, the Filesystem loader and
29
+ # MemcachedFilesystem loader:
30
+ #
31
+ # require 'ruty'
32
+ # loader = Ruty::Loaders::Filesystem.new(:dirname => './templates')
33
+ # t = loader.get_template('index.html')
34
+ # puts t.render(...)
35
+ #
36
+ # In this example the loader wants you to save your templates in the folder
37
+ # called './templates'. For a list of supported arguments have a look at
38
+ # the module documentation of the loaders.
39
+ #
40
+ # You can easily add loaders yourself. Documentation regarding that is
41
+ # comming... maybe... (hey. the ruby sourcecode isn't documented either)
42
+ #
43
+ # Variables
44
+ # =========
45
+ #
46
+ # Variables look like this: {{ variable }}. When the template engine
47
+ # encounters a variable, it evaluates that variable and replaces it with the
48
+ # result. You can use a dot to access keys, indexes or attributes of a
49
+ # variable.
50
+ #
51
+ # Behind the scenes ruty does the following:
52
+ # - hash key lookup
53
+ # - array index lookup
54
+ # - method call*
55
+ #
56
+ # *ruty only calls methods without arguments and only if the object holding
57
+ # those methods returns true for the ruty_safe? method which is called with
58
+ # the requested method name. Here an example to add support for .downcase
59
+ # and .upcase calls on strings (which is not useful since there are filters
60
+ # which are covered in the next section):
61
+ #
62
+ # class String
63
+ # def ruty_safe? name
64
+ # return [:downcase, :upcase].include?(name)
65
+ # end
66
+ # end
67
+ #
68
+ # Filters
69
+ # =======
70
+ #
71
+ # Variables support filters which modify a variable. Filters look like this:
72
+ #
73
+ # {{ variable|filter1|filter "with", "some", "arguments" }}.
74
+ #
75
+ # As you can see you can chain filters using the pipe (|) symbol. You can
76
+ # also add filters yourself:
77
+ #
78
+ # require 'ruty'
79
+ #
80
+ # class MyFilters < Ruty::FilterCollection
81
+ # def my_filter context, value, *arguments
82
+ # "#{value} called with #{arguments.inspect}"
83
+ # end
84
+ #
85
+ # Ruty::Filters.register_collection(self)
86
+ # end
87
+ #
88
+ # Now the filter my_filter will be available in all templates. The first
89
+ # argument of a filter is always the context, an object holding the stacked
90
+ # namespace passed to the template. Usually you don't have to access it but
91
+ # sometimes it might be useful.
92
+ #
93
+ # The following filters exist by default
94
+ #
95
+ # lower
96
+ # -----
97
+ # convert a value to lowercase
98
+ #
99
+ # upper
100
+ # -----
101
+ # convert a value to uppercase
102
+ #
103
+ # capitalize
104
+ # ----------
105
+ # capitalizes a string
106
+ #
107
+ # truncate n=80, ellipsis='...'
108
+ # -----------------------------
109
+ # truncates a string to n characters. If the string was truncated
110
+ # the ellipsis is appended.
111
+ #
112
+ # join char=''
113
+ # ------------
114
+ # joins an array with a given string.
115
+ #
116
+ # sort
117
+ # ----
118
+ # return a sorted version of the value if sorting is supported
119
+ #
120
+ # reverse
121
+ # -------
122
+ # reverses an item if this is supported
123
+ #
124
+ # first
125
+ # -----
126
+ # return the first item of a array
127
+ #
128
+ # last
129
+ # ----
130
+ # return the last item of an array
131
+ #
132
+ # escape attribute=false
133
+ # ----------------------
134
+ # xml-escape a string. If attribute is true it will also escape
135
+ # " to &quot;
136
+ #
137
+ # urlencode
138
+ # ---------
139
+ # urlencodes a string (" " will be converted to "%20" etc.)
140
+ #
141
+ # length
142
+ # ------
143
+ # return the length of an item with a length, 0 else
144
+ #
145
+ #
146
+ # Comments
147
+ # ========
148
+ #
149
+ # Comments look like this:
150
+ #
151
+ # {# this is a comment #}
152
+ #
153
+ # Tags
154
+ # ====
155
+ #
156
+ # Tags look pretty much like variables but they use a percentage sign instead
157
+ # of a second brace:
158
+ #
159
+ # {% tag some, arguments %}
160
+ #
161
+ # Some tags require beginning and ending tags:
162
+ #
163
+ # {% tag %}
164
+ # ... tag contents ...
165
+ # {% endtag %}
166
+ #
167
+ # Here a list of builtin tags:
168
+ #
169
+ # For Loop
170
+ # --------
171
+ #
172
+ # The for loop is useful if you want to iterate over something that
173
+ # supports iteration. For example arrays:
174
+ #
175
+ # <ul>
176
+ # {% for item in iterable %}
177
+ # <li>{{ item|escape }}</li>
178
+ # {% endfor %}
179
+ # </ul>
180
+ #
181
+ # Inside of a loop you have access to some loop variables:
182
+ #
183
+ # loop.index The current iteration of the loop (1-indexed)
184
+ # loop.index0 The current iteration of the loop (0-indexed)
185
+ # loop.revindex The number of iterations from the end of
186
+ # the loop (1-indexed)
187
+ # loop.revindex0 The number of iterations from the end of the
188
+ # loop (0-indexed)
189
+ # loop.first true if this is the first time through the loop
190
+ # loop.last true if this is the last time through the loop
191
+ # loop.even true if this is an even iteration
192
+ # loop.odd true if this is an odd iteration
193
+ # loop.parent For nested loops, this is the loop "above" the
194
+ # current one
195
+ #
196
+ # For loops also have an optional else block that is just rendered
197
+ # if the iteration was empty:
198
+ #
199
+ # {% for user in users %}
200
+ # ...
201
+ # {% else %}
202
+ # no users found
203
+ # {% endfor %}
204
+ #
205
+ # Important Note: Ruty requires objects to not only provide a valid
206
+ # each method for iteration but also a size or length method that
207
+ # returns the number of items. If size and length isn't provided
208
+ # ruty fails silently and renders the else block if given. The same
209
+ # happens if you try to iterate over a number or any other object
210
+ # without an each method.
211
+ #
212
+ # If Conditions
213
+ # -------------
214
+ #
215
+ # If Conditions are very low featured, they only support boolean
216
+ # checks. But they do boolean checks a clever way, so not the ruby
217
+ # way ^^. For example empty objects are considered false, zero, nil
218
+ # and false too.
219
+ #
220
+ # Syntax:
221
+ #
222
+ # {% if item %}
223
+ # ...
224
+ # {% endif %}
225
+ #
226
+ # Or:
227
+ #
228
+ # {% if not item %}
229
+ # ...
230
+ # {% else %}
231
+ # ...
232
+ # {% endif %}
233
+ #
234
+ # There is neither or/and or elsif by now. But that's something that is
235
+ # on the todo list.
236
+ #
237
+ # Cycle
238
+ # -----
239
+ #
240
+ # Cycle among the given objects each time this tag is encountered.
241
+ # Within a loop, cycles among the given strings each time through the loop:
242
+ #
243
+ # {% for item in iterable %}
244
+ # <tr class="{% cycle 'row1', 'row2' %}">
245
+ # ...
246
+ # </tr>
247
+ # {% endfor %}
248
+ #
249
+ # Capture
250
+ # -------
251
+ #
252
+ # Captures the wrapped data and puts it into a variable:
253
+ #
254
+ # {% capture as title %}{% block title %}...{% endblock %}{% endcapture %}
255
+ #
256
+ # That allows using the data of a block multiple times. Note that the
257
+ # variable is only available in the current and lower scopes. If you use
258
+ # this block to capture something inside a for loop tag for example (or
259
+ # inside of a block) it won't be available outside of the loop/block.
260
+ #
261
+ # Ifchanged
262
+ # ---------
263
+ #
264
+ # Check if a value has changed from the last iteration of a loop. If
265
+ # a variable is given as first argument it's used for testing, otherwise
266
+ # the output of the tag:
267
+ #
268
+ # {% for day in days %}
269
+ # {% ifchanged %}<h3>{{ date.hour }}</h3>{% endifchanged %}
270
+ # ...
271
+ # {% endfor %}
272
+ #
273
+ # Or:
274
+ #
275
+ # {% for day in days %}
276
+ # {% ifchanged date.hour %}
277
+ # ...
278
+ # {% endifchanged %}
279
+ # {% endfor %}
280
+ #
281
+ # Giving the tag a variable to check against will speed out the rendering
282
+ # process.
283
+ #
284
+ # Filter
285
+ # ------
286
+ #
287
+ # Applies some filters on the wrapped content:
288
+ #
289
+ # {% filter upper|escape %}
290
+ # <some content here & that includes < invalid
291
+ # html we want to escape and > convert to uppercase
292
+ # {% endfilter %}
293
+ #
294
+ # Debug
295
+ # -----
296
+ #
297
+ # This tag outputs a pretty printed represenation of the context
298
+ # passed to the template:
299
+ #
300
+ # {% debug %}
301
+ #
302
+ # If you want to use the output in a html document use this to get a
303
+ # readable output:
304
+ #
305
+ # <pre>{% filter escape %}{% debug %}{% endfilter %}</pre>
306
+ #
307
+ # Include
308
+ # -------
309
+ #
310
+ # If the template was loaded by a loader it can include other templates:
311
+ #
312
+ # {% include 'name_of_other_template.html' %}
313
+ #
314
+ # Template import paths are usually relative, some loaders might redefine
315
+ # that behavior.
316
+ #
317
+ # Block / Extends
318
+ # ---------------
319
+ #
320
+ # Now to the template inheritance system. Template inheritance allows you
321
+ # to build a base "skeleton" template that contains all the common elements
322
+ # of your site and defines blocks that child templates can override:
323
+ #
324
+ # <html>
325
+ # <head>
326
+ # {% block head %}
327
+ # <title>{% block title %}My Webpage{% endblock %}</title>
328
+ # {% endblock %}
329
+ # </head>
330
+ # <body>
331
+ # <div class="header">...</div>
332
+ # <div class="body">{% block body %}{% endblock %}</div>
333
+ # </body>
334
+ # </html>
335
+ #
336
+ # Saved as layout.html it can act as a layout template for the child template
337
+ # (for example called userlist.html):
338
+ #
339
+ # {% extends 'layout.html' %}
340
+ # {% block title %}Userlist | {{ block.super }}{% endblock %}
341
+ # {% block body %}
342
+ # <ul>
343
+ # {% for user in users %}
344
+ # <li>{{ user|escape }}</li>
345
+ # {% endfor %}
346
+ # </ul>
347
+ # {% endblock %}
348
+ #
349
+ # As you can see block override each other, because of that block names
350
+ # must be unique! You can render the output of an overridden block by
351
+ # outputting block.super. The name of a block is available as block.name,
352
+ # the depth of the current inheritance as block.depth.
353
+ #
354
+ # Note that extends must be the first tag of a template. Otherwise the
355
+ # whole process fails with an error message.
356
+ #
357
+ # Ruty Namespace
358
+ # ==============
359
+ #
360
+ # Inside the context there is a special key ruty which gives you access
361
+ # to some ruty information:
362
+ #
363
+ # ruty.block_start the string representing a block start ( {% )
364
+ # ruty.block_end same for block end ( %} )
365
+ # ruty.var_start same for variable start ( {{ )
366
+ # ruty.var_end same for variable end ( }} )
367
+ # ruty.comment_start same for comments ( {# }
368
+ # ruty.comment_end end comment ends ( #} )
369
+ # ruty.version the ruty version as string
370
+ #
371
+ # Extending Ruty
372
+ # ==============
373
+ #
374
+ # Is very easy. Have a look at the sourcecode... dumdidum. there is no
375
+ # documentation ^^
376
+
377
+
378
+ module Ruty
379
+
380
+ # Returns version of the ruty template engine
381
+ def self.version
382
+ "ruty.rb 0.0.1"
383
+ end
384
+
385
+ # ruty base exception
386
+ class Exception < ::Exception
387
+ end
388
+
389
+ # exception for runtime errors
390
+ class TemplateRuntimeError < Exception
391
+ end
392
+
393
+ # exception for syntax errors
394
+ class TemplateSyntaxError < Exception
395
+ end
396
+
397
+ # exception to indicate that a template a loader
398
+ # tried to load does not exist.
399
+ class TemplateNotFound < Exception
400
+ end
401
+
402
+ # load libraries
403
+ require 'ruty/constants'
404
+ require 'ruty/parser'
405
+ require 'ruty/context'
406
+ require 'ruty/datastructure'
407
+ require 'ruty/loaders'
408
+ require 'ruty/filters'
409
+ require 'ruty/tags'
410
+
411
+ # ruty context
412
+ RUTY_CONTEXT = {
413
+ :block_start => Constants::BLOCK_START,
414
+ :block_end => Constants::BLOCK_END,
415
+ :var_start => Constants::VAR_START,
416
+ :var_end => Constants::VAR_END,
417
+ :comment_start => Constants::COMMENT_START,
418
+ :comment_end => Constants::COMMENT_END,
419
+ :version => Ruty.version
420
+ }
421
+
422
+ # template class
423
+ class Template
424
+
425
+ # load a template from a sourcecode or nodelist.
426
+ def initialize source
427
+ if source.is_a?(Datastructure::NodeList)
428
+ @nodelist = source
429
+ else
430
+ @nodelist = Parser.new(source).parse
431
+ end
432
+ end
433
+
434
+ # render the template. Pass it a hash or hashlike
435
+ # object (must support [] and has_key?) which is
436
+ # used as data storage for the root namespace
437
+ def render namespace
438
+ context = Context.new(namespace)
439
+ context.push(
440
+ :ruty => RUTY_CONTEXT,
441
+ :nil => nil,
442
+ :true => true,
443
+ :false => false
444
+ )
445
+ result = ''
446
+ @nodelist.render_node(context, result)
447
+ result
448
+ end
449
+ end
450
+ end
@@ -0,0 +1,23 @@
1
+ # = Ruty Constants
2
+ #
3
+ # Author:: Armin Ronacher
4
+ #
5
+ # Copyright (c) 2006 by Armin Ronacher
6
+ #
7
+ # You can redistribute it and/or modify it under the terms of the BSD license.
8
+
9
+ module Ruty::Constants
10
+
11
+ # template tags.
12
+ # The ruty parser uses the values here to verify which token it
13
+ # should use. It checks for the token types in the order block,
14
+ # var, comment, text. If for example comment is {{# and block is
15
+ # {{, comment will never match because block is matched first.
16
+ BLOCK_START = '{%'
17
+ BLOCK_END = '%}'
18
+ VAR_START = '{{'
19
+ VAR_END = '}}'
20
+ COMMENT_START = '{#'
21
+ COMMENT_END = '#}'
22
+
23
+ end