PageTemplate 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
1
+ # htmltemplate.rb
2
+ #
3
+ # An emulation layer on top of PT to mimic HTML::Template
4
+ #
5
+
6
+ ############################################################
7
+
8
+ class PageTemplate
9
+ class HTGlossary < SyntaxGlossary
10
+ @directive = /<!--(.+?)-->/m
11
+
12
+ default { |command,parser|
13
+ TextCommand.new("<!--#{command}-->",parser)
14
+ }
15
+
16
+ define(/^tmpl_var (?:name=)?"?((?:\w+)(?:\.\w+\??)*)"?(?:\s+processor="?(\w+)"?)?$/i) {
17
+ |match,parser|
18
+ # Value, Preprocessor, Parser
19
+ ValueCommand.new(match[1],match[2],parser)
20
+ }
21
+
22
+ define(/^(tmpl_if|tmpl_unless) (?:name=)?"?((\w+)(\.\w+\??)*)"?$/i) {
23
+ |match,parser|
24
+ # Called_As, Value, parser
25
+ IfCommand.new(match[1],match[2],parser)
26
+ }
27
+
28
+ define(/^(tmpl_loop) (?:name=)"?(\w+(?:\.\w+\??)*)"?(?:\s+iterators?="?((?:\s*\w+)+)"?)?$/i) {
29
+ |match,parser|
30
+ # Called_As, Value, Iterators
31
+ LoopCommand.new(match[1],match[2],match[3])
32
+ }
33
+
34
+ define(/^tmpl_include (?:name=|file=)?"?((\w+)(?:\.\w+\??)*)"?$/i) {
35
+ |match,parser|
36
+ # Value, parser
37
+ IncludeCommand.new(match[1],parser)
38
+ }
39
+
40
+ # Command#else's expect only to be called
41
+ modifier(:else) { |cmd,command|
42
+ if command =~ /^tmpl_else$/i
43
+ cmd.else
44
+ true
45
+ else
46
+ false
47
+ end
48
+ }
49
+ modifier(:end) { |cmd,command|
50
+ if command =~ /^\/\s*#{cmd.called_as}$/i
51
+ cmd.end
52
+ true
53
+ else
54
+ false
55
+ end
56
+ }
57
+ modifier(:when) { |cmd,command|
58
+ case command
59
+ when /^tmpl_when\s+(?:name=)"?(\w(?:.\w+\??)*)"?$/i
60
+ cmd.when($1)
61
+ true
62
+ when /^tmpl_else$/i
63
+ cmd.else($1)
64
+ true
65
+ else
66
+ false
67
+ end
68
+ }
69
+ end
70
+ end
@@ -19,22 +19,19 @@ class PageTemplate
19
19
  #
20
20
  # Cache: A cache ensures that a method on an object will only be
21
21
  # called once.
22
- class Namespace
22
+ module NamespaceItem
23
23
  attr_accessor :parent, :object
24
- def initialize(parent=nil,object=nil)
25
- @values = Hash.new
26
- @parent = parent
27
- @object = object
28
- end
29
24
 
30
25
  # Clears the cache
31
- def clear_cache
26
+ def clear
32
27
  @values = Hash.new
33
28
  end
29
+ alias_method :clear_cache, :clear
34
30
 
35
31
  # Saves a variable +key+ as the string +value+ in the global
36
32
  # namespace.
37
33
  def set(key, value)
34
+ @values ||= {}
38
35
  @values[key] = value
39
36
  end
40
37
  alias_method :[]=, :set
@@ -54,7 +51,11 @@ class PageTemplate
54
51
  # If +val+ is a dot-separated list of words, then 'key' is the
55
52
  # first part of it. The remainder of the words are sent to key
56
53
  # (and further results) to premit accessing attributes of objects.
57
- def get(val)
54
+ def get(val,clean_rescue=true)
55
+ args = parser.args
56
+ clean_rescue = !args['raise_on_error'] if clean_rescue
57
+ @values ||= {}
58
+ @object ||= nil
58
59
  # Is it ours, or is it cached?
59
60
  key, rest = val.split(/\./,2)
60
61
  value = case
@@ -62,13 +63,14 @@ class PageTemplate
62
63
  @values[key]
63
64
  when @object.respond_to?(:has_key?) && @object.has_key?(key)
64
65
  @values[key] = @object[key]
65
- when @object && @object.respond_to?(key.to_sym)
66
+ when @object.respond_to?(key.to_sym)
66
67
  # This sets the cache and returns the value, too!
67
68
  @values[key] = @object.send(key)
68
69
  when @object && key == '__ITEM__'
69
70
  @object
70
71
  when @parent
71
- @parent.get(key)
72
+ # Return what the parent gets
73
+ return @parent.get(val)
72
74
  else
73
75
  # We're the global namespace
74
76
  nil
@@ -77,21 +79,68 @@ class PageTemplate
77
79
  if rest
78
80
  rest.split(/\./).each do |i|
79
81
  begin
80
- value = value.send(i)
82
+ value = case
83
+ when value.respond_to?(:has_key?) && value.has_key?(i)
84
+ value[i]
85
+ when value.respond_to?(:[]) && i =~ /^\d+$/
86
+ value[i.to_i]
87
+ when value.respond_to?(i)
88
+ value.send(i)
89
+ else
90
+ nil
91
+ end
81
92
  rescue NoMethodError => er
82
93
  return nil
83
94
  end
84
95
  end
85
96
  end
86
97
  value
98
+ rescue Exception => e
99
+ if clean_rescue
100
+ "[ Error: #{e.message} ]"
101
+ else
102
+ raise e
103
+ end
87
104
  end
88
105
  alias_method :[], :get
89
106
 
107
+ # parser: most namespace objects won't be a parser, but pass this
108
+ # query up to the parser object.
109
+ def parser
110
+ if @parent
111
+ @parent.parser
112
+ else
113
+ Parser.recent_parser
114
+ end
115
+ end
116
+
90
117
  # A convenience method to test whether a variable has a true
91
118
  # value. Returns nil if +flag+ is not found in the namespace,
92
119
  # or +flag+ has a nil value attached to it.
93
120
  def true?(flag)
94
- get(flag) ? true : false
121
+ args = parser.args
122
+ val = get(flag,false)
123
+ case
124
+ when ! val
125
+ false
126
+ when args['empty_is_true']
127
+ true
128
+ when val.respond_to?(:empty?)
129
+ ! val.empty?
130
+ else
131
+ true
132
+ end
133
+ rescue Exception => er
134
+ false
135
+ end
136
+ end
137
+
138
+ class Namespace
139
+ include NamespaceItem
140
+ def initialize(parent=nil,object=nil)
141
+ @values = Hash.new
142
+ @parent = parent
143
+ @object = object
95
144
  end
96
145
  end
97
146
 
@@ -120,7 +169,7 @@ class PageTemplate
120
169
  @args = args
121
170
  @paths = @args['include_paths']
122
171
  @paths ||= [@args['include_path']].compact
123
- @paths = [Dir.getwd] if @paths.empty?
172
+ @paths = [Dir.getwd,'/'] if @paths.empty?
124
173
  end
125
174
 
126
175
  # Return the contents of the file +name+, which must be within the
@@ -158,54 +207,147 @@ class PageTemplate
158
207
  # regexps should not contain PageTemplate command text. i.e:
159
208
  # /var \w+/i should be used instead of /[% var %]/
160
209
  class SyntaxGlossary
161
- attr_accessor :directive
162
-
163
- def initialize(directive, glossary)
164
- @directive = directive
165
- @glossary = glossary
166
- end
210
+ class << self
211
+ attr_accessor :directive
212
+ attr_writer :default
213
+ def default(&block)
214
+ if block_given?
215
+ @default = block
216
+ else
217
+ @default
218
+ end
219
+ end
167
220
 
168
- # Look up +text+ to see if it matches a command within the lookup
169
- # table, returning the command for it, or UnknownCommand if none
170
- # match.
171
- def lookup(text)
172
- @glossary.each do |key,val|
173
- if m = key.match(text)
174
- return val
221
+ # Look up +command+ to see if it matches a command within the lookup
222
+ # table, returning the instance of the command for it, or
223
+ # an UnknownCommand if none match.
224
+ def lookup(command)
225
+ @glossary.each do |key,val|
226
+ if m = key.match(command)
227
+ return val.call(m)
228
+ end
175
229
  end
230
+
231
+ return @default.call(command)
232
+ end
233
+ def modifies?(modifier,cmd,command)
234
+ @modifiers[modifier].call(cmd,command)
235
+ end
236
+
237
+ # Define a regexp -> Command mapping.
238
+ # +rx+ is inserted in the lookup table as a key for +command+
239
+ def define(rx,&block)
240
+ raise ArgumentError, 'First argument to define must be a Regexp' unless rx.is_a?(Regexp)
241
+ raise ArgumentError, 'Block expected' unless block
242
+ @glossary ||= {}
243
+ @glossary[rx] = block
176
244
  end
177
245
 
178
- return UnknownCommand
179
- end
180
-
181
- # Define a regexp -> Command mapping.
182
- # +rx+ is inserted in the lookup table as a key for +command+
183
- def define(rx,command)
184
- raise ArgumentError, 'First argument to define must be a Regexp' unless rx.is_a?(Regexp)
185
- @glossary[rx] = command
186
- end
187
- # This is shorthand for define(+key+,Valuecommand), and also
188
- # allows +key+ to be a string, converting it to a regexp before
189
- # adding it to the dictionary.
190
- def define_global_var(key)
191
- rx = key
192
- rx = /^#{key.to_s}(?:\.\w+\??)*(?:\s:(\w+))?$/ unless key.is_a?(Regexp)
193
- @glossary[rx] = ValueCommand
246
+ def modifier(sym,&block)
247
+ raise ArgumentError, 'First argument to define must be a Symbol' unless sym.is_a?(Symbol)
248
+ raise ArgumentError, 'Block expected' unless block
249
+ @modifiers ||= Hash.new(lambda { false })
250
+ @modifiers[sym] = block
251
+ end
252
+
253
+ # This is shorthand for define(+key+,Valuecommand), and also
254
+ # allows +key+ to be a string, converting it to a regexp before
255
+ # adding it to the dictionary.
256
+ def define_global_var(rx)
257
+ @glossary ||= {}
258
+ rx = /^(#{key.to_s}(?:\.\w+\??)*)(?:\s:(\w+))?$/ unless rx.is_a?(Regexp)
259
+ @glossary[rx] = lambda { |match|
260
+ ValueCommand.new(match[1],match[2])
261
+ }
262
+ end
194
263
  end
195
264
  end
196
265
 
197
266
  # This is the regexp we tried using for grabbing an entire line
198
267
  # Good for everything but ValueCommand :-/.
199
268
  # /(?:^\s*\[%([^\]]+?)%\]\s*$\r?\n?|\[%(.+?)%\])/m,
200
- DEFAULTGLOSSARY = SyntaxGlossary.new(
201
- /\[%(.+?)%\]/m,
269
+ class DefaultGlossary < SyntaxGlossary
270
+ @directive = /\[%(.+?)%\]/m
271
+
272
+ default { |command|
273
+ UnknownCommand.new(command)
274
+ }
275
+
276
+ define(/^var ((?:\w+)(?:\.\w+\??)*)(?:\s+:(\w+))?$/i) { |match|
277
+ # Value, Preprocessor
278
+ ValueCommand.new(match[1],match[2])
279
+ }
280
+
281
+ define(/^--(.+)$/i) { |match|
282
+ # The Comment
283
+ CommentCommand.new(match[1])
284
+ }
285
+
286
+ define(/^(if|unless) ((\w+)(\.\w+\??)*)$/i) { |match|
287
+ # Called_As, Value
288
+ IfCommand.new(match[1],match[2])
289
+ }
290
+
291
+ define(/^(in|loop) (\w+(?:\.\w+\??)*)(?:\:((?:\s+\w+)+))?$/i) { |match|
292
+ # Called_As, Value, Iterators
293
+ LoopCommand.new(match[1],match[2],match[3])
294
+ }
295
+
296
+ define(/^include ((\w+)(?:\.\w+\??)*)$/i) { |match|
297
+ # Value
298
+ IncludeCommand.new(match[1])
299
+ }
300
+
301
+ # Command#else's expect only to be called
302
+ modifier(:else) { |cmd,command|
303
+ case command
304
+ when /^else|no|empty$/i
305
+ cmd.else
306
+ true
307
+ else
308
+ false
309
+ end
310
+ }
311
+
312
+ # elsif: accepts else and elsif
313
+ modifier(:elsif) { |cmd,command|
314
+ case command
315
+ when /^(?:elsif|elseif|else if) (.+)$/i
316
+ cmd.elsif($1)
317
+ true
318
+ when /^else|no|empty$/i
319
+ cmd.else
320
+ true
321
+ else
322
+ false
323
+ end
324
+ }
202
325
 
203
- /^var ((\w+)(\.\w+\??)*)(\s+:\w+)?$/i => ValueCommand,
204
- /^--/i => CommentCommand,
205
- /^(if|unless) ((\w+)(\.\w+\??)*)$/i => IfCommand,
206
- /^(in|loop) ((\w+)(\.\w+\??)*)(:\s+\w+)?$/i => LoopCommand,
207
- /^(include) ((\w+)(\.\w+\??)*)$/i => IncludeCommand
208
- )
326
+ # Command#end
327
+ # accepts 'end', 'end(name)' and 'end (name)'
328
+ modifier(:end) { |cmd,command|
329
+ case command
330
+ when /^end\s*(#{cmd.called_as})?$/i
331
+ cmd.end
332
+ true
333
+ else
334
+ false
335
+ end
336
+ }
337
+ # For case statements.
338
+ modifier(:when) { |cmd,command|
339
+ case command
340
+ when /^when\s+(\w(?:.\w+\??)*)$/i
341
+ cmd.when($1)
342
+ true
343
+ when /^else$/i
344
+ cmd.else($1)
345
+ true
346
+ else
347
+ false
348
+ end
349
+ }
350
+ end
209
351
 
210
352
  # Preprocessor is a parent class for preprocessors.
211
353
  # A preprocessor is used to process or otherwise prepare output for
@@ -249,7 +391,7 @@ class PageTemplate
249
391
  # keys: (with defaults)
250
392
  #
251
393
  # 'namespace' => A namespace object. (A new namespace)
252
- # 'glossary' => A SyntaxGlossary object. (a dup of DEFAULTGLOSSARY)
394
+ # 'glossary' => A SyntaxGlossary class singleton. (DefaultGlossary)
253
395
  # 'preprocessor' => The preprocessor. (DefaultPreprocessor)
254
396
  # 'default_processor' => The processor. (:process)
255
397
  # 'source' => The Source for templates. (FileSource)
@@ -268,6 +410,12 @@ class PageTemplate
268
410
  attr_reader :preprocessor, :default_processor
269
411
  attr_reader :glossary, :namespace, :source
270
412
  attr_reader :args, :commands
413
+
414
+ # This is corny, but recent_parser returns the most recently created
415
+ # Parser.
416
+ def Parser.recent_parser
417
+ @@recent_parser
418
+ end
271
419
  # Parser.new() accepts a hash as an argument, and looks for these
272
420
  # keys: (with defaults)
273
421
  #
@@ -277,9 +425,11 @@ class PageTemplate
277
425
  # 'default_processor' => The processor. (:process)
278
426
  # 'source' => The Source for templates. (FileSource)
279
427
  def initialize(args = {})
428
+ @namespace = self
429
+ @@recent_parser = self
280
430
  @args = args # For sub-commands
281
- @namespace = args['namespace'] || Namespace.new
282
- @glossary = args['glossary'] || DEFAULTGLOSSARY.dup
431
+ @parent = args['namespace'] || nil
432
+ @glossary = args['glossary'] || DefaultGlossary
283
433
  @preprocessor = args['preprocessor'] || DefaultPreprocessor
284
434
  @default_processor = args['default_processor'] || :process
285
435
  @source = (args['source'] || FileSource).new(@args)
@@ -304,6 +454,10 @@ class PageTemplate
304
454
  def parse(body)
305
455
  rx = @glossary.directive
306
456
  stack = [Template.new(self)]
457
+ stack[0].parent = self
458
+ last = stack.last
459
+ modifier = nil
460
+ closer = nil
307
461
  while (m = rx.match(body))
308
462
  pre = m.pre_match
309
463
  command = m[1].strip.gsub(/\s+/,' ')
@@ -316,31 +470,28 @@ class PageTemplate
316
470
 
317
471
  # If the command at the top of the stack is a 'Stacker',
318
472
  # Does this command modify it? If so, just skip back.
319
- next if stack.last.modifies?(command)
473
+ next if modifier && @glossary.modifies?(modifier,last,command)
320
474
 
321
475
  # If it closes, we're done changing this. Pop it off the
322
476
  # Stack and add it to the one above it.
323
- if stack.last.closes?(command)
477
+ if closer and @glossary.modifies?(closer,last,command)
324
478
  cmd = stack.pop
325
- stack.last.add(cmd)
479
+ last = stack.last
480
+ last.add(cmd)
326
481
  next
327
482
  end
328
483
 
329
- # Find what command it is.
484
+ # Create the command
330
485
  cmd = @glossary.lookup(command)
331
486
 
332
- # If the command takes it, pass the parser.
333
- block = if (cmd.instance_method(:initialize).arity == 2)
334
- cmd.new(command,self)
335
- else
336
- cmd.new(command)
337
- end
338
-
339
487
  # If it's a stacking command, push it on the stack
340
- if block.is_a?(StackableCommand)
341
- stack.push block
488
+ if cmd.is_a?(StackableCommand)
489
+ modifier = cmd.class.modifier
490
+ closer = cmd.class.closer
491
+ stack.push cmd
492
+ last = cmd
342
493
  else
343
- stack.last.add block
494
+ last.add cmd
344
495
  end
345
496
  end
346
497
  stack.last.add TextCommand.new(body) if body && body.length > 0
@@ -349,25 +500,22 @@ class PageTemplate
349
500
  end
350
501
  stack[0]
351
502
  end
503
+
504
+ # Since a Parser is also a namespace object, include NamespaceItem
505
+ include NamespaceItem
506
+ # But redefine parser
507
+ def parser
508
+ self
509
+ end
510
+
352
511
  # Not really of any point, but clears the saved commands.
353
512
  def clearCommands
354
513
  @commands = nil
355
514
  end
356
- # Sets this parser's namespace values.
357
- def []=(key,val)
358
- @namespace[key] = val
359
- end
360
- # Gets this parser's namespace values.
361
- def [](key)
362
- @namespace[key]
363
- end
364
515
  # If any commands are loaded and saved, return a string of it.
365
- def output(namespace=nil)
516
+ def output(*args)
366
517
  return '' unless @commands
367
- @namespace.parent = namespace if namespace
368
- str = @commands.output(@namespace)
369
- @namespace.parent = nil
370
- str
518
+ @commands.output(*args)
371
519
  end
372
520
  end
373
521
  end