Class Tangerine
In: lib/nukumi2/page.rb
lib/nukumi2/vendor/tangerine.rb
Parent: Object

Tangerine — A general-purpose templating system Copyright (C) 2004 Christian Neukirchen <purl.org/net/chneukirchen>

Licensed under the same terms as Ruby itself.

Methods

Classes and Modules

Class Tangerine::FileLib
Class Tangerine::RFC822
Class Tangerine::XML

Constants

VERSION = "0.3.1"   Version information

Attributes

code  [R]  The generated code.
taglib  [RW]  The tag library being used.

Public Class methods

Indent the string to depth, only subsequent lines.

[Source]

    # File lib/nukumi2/vendor/tangerine.rb, line 19
19:   def self.indent(depth, string)
20:     string.gsub(/\n^/, "\n" + ' '*depth)
21:   end

Create, parse and compile a new Tangerine template given in template.

[Source]

    # File lib/nukumi2/vendor/tangerine.rb, line 24
24:   def initialize(template, taglib=nil, indent=0)
25:     super template
26:     
27:     @taglib = taglib
28:     @indent = indent
29: 
30:     @uid = (@@uid += 1)
31:     @code = ""
32: 
33:     generate_header
34:     generate_code
35:     generate_footer
36:     compile
37:   end

Public Instance methods

Compile generated code into a Proc.

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 173
173:   def compile
174:     begin
175:       @template = eval @code, nil, "(tangerine)", 1
176:     rescue SyntaxError
177:       raise
178:         "Internal error during compilation. Please submit a bugreport.\n" << $!
179:     end
180:   end

[Source]

   # File lib/nukumi2/page.rb, line 5
5:   def content_type
6:     @taglib.resolve("content-type").strip
7:   end

Return a string to be appended to string in order to return code that performs output escaping.

[Source]

    # File lib/nukumi2/vendor/tangerine.rb, line 41
41:   def escaper
42:     ""
43:   end

Expand the template for object by calling given methods and inline code. Output gets sent to output using <<. If called with only one element, a string holding the result is returned.

[Source]

    # File lib/nukumi2/vendor/tangerine.rb, line 48
48:   def expand(output, object=nil)
49:     if object.nil?
50:       object = output
51:       output = ""
52:     end
53: 
54:     begin
55:       @template.call output, object
56:     rescue => exception
57:       raise exception.exception("During template expansion:\n" <<
58:         "    #{exception}\n" <<
59:         "    #{Tangerine.indent 4, exception.backtrace.join("\n")}\n")
60:     end
61:   end

Parse the template and generate appropriate output code.

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 74
 74:   def generate_code
 75:     @cursor = 0
 76:     @mark = 0
 77:     
 78:     while @cursor = index(',,', @cursor)
 79:       # Skip second comma.
 80:       skip
 81: 
 82:       # Do comma escaping?
 83:       if peek == ?,
 84:         skip  while peek && peek == ?,
 85:         emit_text selection
 86:         @mark = @cursor
 87:         next
 88:       else
 89:         emit_text selection
 90:       end
 91: 
 92:       # Skip comment?
 93:       if peek == ?#
 94:         @mark = @cursor = index("\n", @cursor) + 1
 95:         emit_code "\n"          # Keep lineno uptodate.
 96:         next
 97:       end
 98: 
 99:       # Initialize local variables.
100:       method = ""
101:       resolve = false
102:       indent = get_indentation
103:       escape = get_escape
104:       ignore_result = get_ignore_result
105: 
106:       case peek
107:       when ?@
108:         # Use taglib ...
109:         skip
110:         resolve = true
111:         tag = get_tag
112:         raise 'Tag not resolvable: foo'  unless @taglib
113:         expansion = @taglib.resolve(tag)
114:       when ?(
115:         # ... or instance_eval ...
116:         skip
117:         method = read_to '('[0], ')'[0]
118:       when ?[
119:         # Use instance_eval + refhack
120:         skip
121:         method = "self" + read_to('['[0], ']'[0])
122:       else
123:         # ... or send?
124:         method = get_tag
125:       end
126: 
127:       # Read block?
128:       block = read_block
129: 
130:       skip
131: 
132:       if indent == 0
133:         if @indent != 0
134:           i = "Tangerine.indent(#{@indent}, "
135:         else
136:           i = "("
137:         end
138:       else
139:         i = "Tangerine.indent(#{indent}, "
140:       end
141: 
142:       if resolve
143:         if block
144:           # + 1 for |
145:           emit_code "#{result_variable} = #{i}" + self.class.new(expansion, @taglib, indent+1).code + ".call('', #{object_variable})); ";
146:         else
147:           emit_code self.class.new(expansion, @taglib, indent).code + ".call(#{output_variable}, #{object_variable}); ";
148:         end
149:       elsif method =~ /^[\w_]+[!?]?$/
150:         emit_code "#{result_variable} = #{object_variable}.#{method}; "
151:       else
152:         emit_code "#{result_variable} = #{object_variable}.instance_eval(#{method.dump}); "
153:       end
154: 
155:       unless ignore_result
156:         if block
157:           emit_code "#{template_variable} = " + self.class.new(block, @taglib, indent).code
158:           emit_code "; ((#{result_variable} && [*(#{result_variable} == true ? [#{object_variable}] : #{result_variable})].each { |d| #{template_variable}.call(#{output_variable}, d)}).to_s#{escape}); "
159:         elsif not resolve
160:           emit_code "#{output_variable} << #{i}#{result_variable}.to_s#{escape}); "
161:         end
162:       end
163: 
164:       @mark = @cursor
165:     end
166:     
167:     @cursor = -1  if @cursor.nil?
168: 
169:     emit_text selection_to_end
170:   end

Emit the code footer.

[Source]

    # File lib/nukumi2/vendor/tangerine.rb, line 69
69:   def generate_footer
70:     emit_code "#{output_variable} }"
71:   end

Emit the code header.

[Source]

    # File lib/nukumi2/vendor/tangerine.rb, line 64
64:   def generate_header
65:     emit_code "lambda { |#{output_variable}, #{object_variable}| "
66:   end

Private Instance methods

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 221
221:   def backskip
222:     @cursor -= 1
223:   end

Generate code c.

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 280
280:   def emit_code(c)
281:     @code << c
282:   end

Generate output code to emit t.

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 285
285:   def emit_text(t)
286:     emit_code "#{output_variable} << #{t.dump}; "
287:     emit_code "\n" * t.count("\n")
288:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 244
244:   def get_escape
245:     # Disable escaping?
246:     if peek == ?!
247:       skip
248:       ""
249:     else
250:       escaper
251:     end
252:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 254
254:   def get_ignore_result
255:     if peek == ?:
256:       skip
257:       true
258:     else
259:       false
260:     end
261:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 234
234:   def get_indentation
235:     # Indent like this line?
236:     if peek == ?|
237:       skip
238:       @cursor - (self.rindex("\n", @cursor) || 0) - 3
239:     else
240:       0
241:     end
242:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 263
263:   def get_tag
264:     tag = ""
265:     while peek && peek.chr =~ /[\w0-9_?!.]/
266:       tag << getc
267:     end
268: 
269:     # Allow dots, but skip them if last char.
270:     if tag[-1] == ?.
271:       backskip
272:       tag[0..-2]
273:     else
274:       tag
275:     end
276:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 212
212:   def getc
213:     @cursor += 1
214:     self[@cursor]
215:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 184
184:   def object_variable
185:     "_o#{@uid}"
186:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 196
196:   def output_variable
197:     "_t#{@uid}"
198:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 208
208:   def peek
209:     self[@cursor+1]
210:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 225
225:   def read_block
226:     if peek == ?{
227:       skip
228:       read_to('{'[0], '}'[0])[1...-1].gsub(/\A\n/, '')
229:     else
230:       nil
231:     end
232:   end

Read from the current position to close, ignoring nested open/close.

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 292
292:   def read_to(open, close)
293:     mark = @cursor
294:     
295:     open = open.chr
296:     close = close.chr
297: 
298:     while @cursor = index(close, @cursor+1)
299:       part = self[mark..@cursor]
300:       
301:       return part  if part.count(open) == part.count(close)
302:     end
303: 
304:     raise RuntimeError, "Hit end of template looking for matching `#{close}'."
305:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 192
192:   def result_variable
193:     "_r#{@uid}"
194:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 200
200:   def selection
201:     self[@mark...@cursor-1]
202:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 204
204:   def selection_to_end
205:     self[@mark..-1]
206:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 217
217:   def skip
218:     @cursor += 1
219:   end

[Source]

     # File lib/nukumi2/vendor/tangerine.rb, line 188
188:   def template_variable
189:     "_m#{@uid}"
190:   end

[Validate]