ruty 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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