PageTemplate 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/Changes +24 -0
  2. data/MANIFEST +0 -1
  3. data/README.txt +10 -8
  4. data/Rakefile +30 -9
  5. data/lib/PageTemplate/case.rb +72 -0
  6. data/lib/PageTemplate/commands.rb +500 -0
  7. data/lib/PageTemplate/parser.rb +373 -0
  8. data/lib/PageTemplate.rb +47 -991
  9. data/setup-usage.txt +129 -0
  10. data/setup.rb +1360 -0
  11. data/tdata/dummy.txt +2 -0
  12. data/test.rb +616 -0
  13. metadata +13 -169
  14. data/CVS/Entries +0 -10
  15. data/CVS/Repository +0 -1
  16. data/CVS/Root +0 -1
  17. data/Changes~ +0 -7
  18. data/InstalledFiles +0 -7
  19. data/Rakefile~ +0 -54
  20. data/TC_PageTemplate.rb +0 -974
  21. data/TC_PageTemplate.rb~ +0 -965
  22. data/config.save +0 -12
  23. data/doc/classes/BlockCommand.html +0 -229
  24. data/doc/classes/BlockCommand.src/M000004.html +0 -18
  25. data/doc/classes/BlockCommand.src/M000005.html +0 -25
  26. data/doc/classes/BlockCommand.src/M000006.html +0 -18
  27. data/doc/classes/BlockCommand.src/M000007.html +0 -23
  28. data/doc/classes/BlockCommand.src/M000008.html +0 -24
  29. data/doc/classes/Command.html +0 -167
  30. data/doc/classes/Command.src/M000032.html +0 -19
  31. data/doc/classes/Command.src/M000033.html +0 -18
  32. data/doc/classes/CommentCommand.html +0 -146
  33. data/doc/classes/CommentCommand.src/M000009.html +0 -18
  34. data/doc/classes/IfCommand.html +0 -193
  35. data/doc/classes/IfCommand.src/M000042.html +0 -19
  36. data/doc/classes/IfCommand.src/M000043.html +0 -20
  37. data/doc/classes/IfCommand.src/M000044.html +0 -20
  38. data/doc/classes/IfElseCommand.html +0 -189
  39. data/doc/classes/IfElseCommand.src/M000025.html +0 -19
  40. data/doc/classes/IfElseCommand.src/M000026.html +0 -23
  41. data/doc/classes/IfElseCommand.src/M000027.html +0 -20
  42. data/doc/classes/IncludeCommand.html +0 -171
  43. data/doc/classes/IncludeCommand.src/M000019.html +0 -26
  44. data/doc/classes/IncludeCommand.src/M000020.html +0 -28
  45. data/doc/classes/IncludeCommand.src/M000021.html +0 -18
  46. data/doc/classes/IncludeCommandError.html +0 -118
  47. data/doc/classes/LoopCommand.html +0 -197
  48. data/doc/classes/LoopCommand.src/M000001.html +0 -19
  49. data/doc/classes/LoopCommand.src/M000002.html +0 -42
  50. data/doc/classes/LoopCommand.src/M000003.html +0 -20
  51. data/doc/classes/LoopElseCommand.html +0 -190
  52. data/doc/classes/LoopElseCommand.src/M000022.html +0 -19
  53. data/doc/classes/LoopElseCommand.src/M000023.html +0 -23
  54. data/doc/classes/LoopElseCommand.src/M000024.html +0 -20
  55. data/doc/classes/Namespace.html +0 -297
  56. data/doc/classes/Namespace.src/M000034.html +0 -19
  57. data/doc/classes/Namespace.src/M000035.html +0 -18
  58. data/doc/classes/Namespace.src/M000036.html +0 -18
  59. data/doc/classes/Namespace.src/M000037.html +0 -19
  60. data/doc/classes/Namespace.src/M000038.html +0 -36
  61. data/doc/classes/Namespace.src/M000039.html +0 -21
  62. data/doc/classes/Namespace.src/M000040.html +0 -18
  63. data/doc/classes/Namespace.src/M000041.html +0 -18
  64. data/doc/classes/PageTemplate.html +0 -380
  65. data/doc/classes/PageTemplate.src/M000010.html +0 -49
  66. data/doc/classes/PageTemplate.src/M000011.html +0 -18
  67. data/doc/classes/PageTemplate.src/M000012.html +0 -29
  68. data/doc/classes/PageTemplate.src/M000013.html +0 -18
  69. data/doc/classes/PageTemplate.src/M000014.html +0 -18
  70. data/doc/classes/PageTemplate.src/M000015.html +0 -18
  71. data/doc/classes/PageTemplate.src/M000016.html +0 -18
  72. data/doc/classes/PageTemplate.src/M000017.html +0 -18
  73. data/doc/classes/PageTemplate.src/M000018.html +0 -19
  74. data/doc/classes/Syntax/CachedParser.html +0 -163
  75. data/doc/classes/Syntax/CachedParser.src/M000048.html +0 -19
  76. data/doc/classes/Syntax/CachedParser.src/M000049.html +0 -53
  77. data/doc/classes/Syntax/Glossary.html +0 -251
  78. data/doc/classes/Syntax/Glossary.src/M000050.html +0 -20
  79. data/doc/classes/Syntax/Glossary.src/M000051.html +0 -29
  80. data/doc/classes/Syntax/Parser.html +0 -261
  81. data/doc/classes/Syntax/Parser.src/M000052.html +0 -22
  82. data/doc/classes/Syntax/Parser.src/M000053.html +0 -36
  83. data/doc/classes/Syntax/Parser.src/M000054.html +0 -18
  84. data/doc/classes/Syntax/Parser.src/M000055.html +0 -21
  85. data/doc/classes/Syntax/Parser.src/M000056.html +0 -53
  86. data/doc/classes/Syntax/Parser.src/M000057.html +0 -111
  87. data/doc/classes/Syntax.html +0 -139
  88. data/doc/classes/TextCommand.html +0 -185
  89. data/doc/classes/TextCommand.src/M000045.html +0 -18
  90. data/doc/classes/TextCommand.src/M000046.html +0 -18
  91. data/doc/classes/TextCommand.src/M000047.html +0 -18
  92. data/doc/classes/UnlessCommand.html +0 -151
  93. data/doc/classes/UnlessCommand.src/M000028.html +0 -20
  94. data/doc/classes/ValueCommand.html +0 -186
  95. data/doc/classes/ValueCommand.src/M000029.html +0 -18
  96. data/doc/classes/ValueCommand.src/M000030.html +0 -18
  97. data/doc/classes/ValueCommand.src/M000031.html +0 -20
  98. data/doc/created.rid +0 -1
  99. data/doc/files/README_txt.html +0 -224
  100. data/doc/files/lib/PageTemplate_rb.html +0 -119
  101. data/doc/fr_class_index.html +0 -44
  102. data/doc/fr_file_index.html +0 -28
  103. data/doc/fr_method_index.html +0 -83
  104. data/doc/index.html +0 -24
  105. data/doc/rdoc-style.css +0 -208
  106. data/install.rb +0 -1015
  107. data/lib/CVS/Entries +0 -2
  108. data/lib/CVS/Repository +0 -1
  109. data/lib/CVS/Root +0 -1
  110. data/lib/PageTemplate.rb~ +0 -1004
  111. data/pkg/PageTemplate-1.2.0.gem +0 -0
  112. data/tdata/CVS/Entries +0 -28
  113. data/tdata/CVS/Repository +0 -1
  114. data/tdata/CVS/Root +0 -1
  115. data/tdata/cm.txt +0 -3
  116. data/tdata/complex.txt +0 -116
  117. data/tdata/i1.txt +0 -1
  118. data/tdata/i2.txt +0 -7
  119. data/tdata/ib1.txt +0 -7
  120. data/tdata/ib2.txt +0 -13
  121. data/tdata/ie1.txt +0 -4
  122. data/tdata/ie2.txt +0 -15
  123. data/tdata/include.1.txt +0 -1
  124. data/tdata/include.2.txt +0 -1
  125. data/tdata/include.3.txt +0 -1
  126. data/tdata/include.4.nf.out.txt +0 -2
  127. data/tdata/include.4.out.txt +0 -3
  128. data/tdata/include.4.txt +0 -2
  129. data/tdata/include.4a.txt +0 -1
  130. data/tdata/include.4b.txt +0 -2
  131. data/tdata/include.5.txt +0 -2
  132. data/tdata/l1.txt +0 -5
  133. data/tdata/l2.txt +0 -7
  134. data/tdata/metadata.1.txt +0 -5
  135. data/tdata/metadata.2.txt +0 -5
  136. data/tdata/metadata.3.txt +0 -5
  137. data/tdata/metadata.4.txt +0 -5
  138. data/tdata/nm.txt +0 -4
  139. data/tdata/p/CVS/Entries +0 -5
  140. data/tdata/p/CVS/Repository +0 -1
  141. data/tdata/p/CVS/Root +0 -1
  142. data/tdata/p/CVS/b2.txt,t +0 -0
  143. data/tdata/p/_b1.txt +0 -0
  144. data/tdata/p/b1.txt +0 -4
  145. data/tdata/p/b2.txt +0 -4
  146. data/tdata/p/b2.txt~ +0 -4
  147. data/tdata/p/cb1.txt +0 -4
  148. data/tdata/t.txt +0 -2
  149. data/tdata/v1.txt +0 -11
  150. data/tdata/v2.txt +0 -11
@@ -0,0 +1,373 @@
1
+ # parser.rb
2
+ #
3
+ # This contains the guts of the engines that make PageTemplate work.
4
+ #
5
+
6
+ ############################################################
7
+
8
+ class PageTemplate
9
+
10
+ # A Namespace object consists of three things:
11
+ #
12
+ # parent: A parent object to get a value from if the namespace
13
+ # does not 'know' a value.
14
+ #
15
+ # object: An object is a hash or list that contains the values that
16
+ # this namespace will refer to. It may also be an object, in which
17
+ # case, its methods are treated as a hash, with respond_to? and
18
+ # send()
19
+ #
20
+ # Cache: A cache ensures that a method on an object will only be
21
+ # called once.
22
+ class Namespace
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
+
30
+ # Clears the cache
31
+ def clear_cache
32
+ @values = Hash.new
33
+ end
34
+
35
+ # Saves a variable +key+ as the string +value+ in the global
36
+ # namespace.
37
+ def set(key, value)
38
+ @values[key] = value
39
+ end
40
+ alias_method :[]=, :set
41
+
42
+ # Returns the first value found for +key+ in the nested namespaces.
43
+ # Returns nil if no value is found.
44
+ #
45
+ # Values are checked for in this order through each level of namespace:
46
+ #
47
+ # * level.key
48
+ # * level.key()
49
+ # * level[key]
50
+ #
51
+ # If a value is not found in any of the nested namespaces, get()
52
+ # searches for the key in the global namespace.
53
+ #
54
+ # If +val+ is a dot-separated list of words, then 'key' is the
55
+ # first part of it. The remainder of the words are sent to key
56
+ # (and further results) to premit accessing attributes of objects.
57
+ def get(val)
58
+ # Is it ours, or is it cached?
59
+ key, rest = val.split(/\./,2)
60
+ value = case
61
+ when @values.has_key?(key)
62
+ @values[key]
63
+ when @object.respond_to?(:has_key?) && @object.has_key?(key)
64
+ @values[key] = @object[key]
65
+ when @object && @object.respond_to?(key.to_sym)
66
+ # This sets the cache and returns the value, too!
67
+ @values[key] = @object.send(key)
68
+ when @object && key == '__ITEM__'
69
+ @object
70
+ when @parent
71
+ @parent.get(key)
72
+ else
73
+ # We're the global namespace
74
+ nil
75
+ end
76
+
77
+ if rest
78
+ rest.split(/\./).each do |i|
79
+ begin
80
+ value = value.send(i)
81
+ rescue NoMethodError => er
82
+ return nil
83
+ end
84
+ end
85
+ end
86
+ value
87
+ end
88
+ alias_method :[], :get
89
+
90
+ # A convenience method to test whether a variable has a true
91
+ # value. Returns nil if +flag+ is not found in the namespace,
92
+ # or +flag+ has a nil value attached to it.
93
+ def true?(flag)
94
+ get(flag) ? true : false
95
+ end
96
+ end
97
+
98
+ # A Source is a source from which templates are drawn.
99
+ #
100
+ # Source#get(name) must return the body of the template that is to
101
+ # be parsed, or nil if it doesn't exist.
102
+ class Source
103
+ def initialize(args = {})
104
+ @args = args
105
+ end
106
+ def get(name)
107
+ name
108
+ end
109
+ end
110
+
111
+ # FileSource provides access to files within the 'include_paths'
112
+ # argument.
113
+ #
114
+ # It attempts to be secure by not allowing any access outside of
115
+ # The directories detailed within include_paths
116
+ class FileSource < Source
117
+ attr_reader :paths
118
+
119
+ def initialize(args = {})
120
+ @args = args
121
+ @paths = @args['include_paths']
122
+ @paths ||= [@args['include_path']].compact
123
+ @paths = [Dir.getwd] if @paths.empty?
124
+ end
125
+
126
+ # Return the contents of the file +name+, which must be within the
127
+ # include_paths.
128
+ def get(name)
129
+ begin
130
+ fn = get_filename(name)
131
+ if fn
132
+ return IO.read(fn)
133
+ else
134
+ return nil
135
+ end
136
+ rescue Exception => er
137
+ return "[ Unable to open file #{fn} ]"
138
+ end
139
+ end
140
+ def get_filename(file)
141
+ file = file.gsub(/\.\.\//,'')
142
+ file.untaint
143
+ @paths.each do |path|
144
+ fn = File.join(path,file)
145
+ return fn if File.exists?(fn)
146
+ end
147
+ return nil
148
+ end
149
+ end
150
+
151
+ # This is the dictionary of commands and the directive that Parser
152
+ # uses to compile a template into a tree of commands.
153
+ #
154
+ # directive is the general format of a PageTemplate command.
155
+ # Default: /\[%(.+?)%\]/m
156
+ #
157
+ # @glossary is a hash table of regexp->Command objects. These
158
+ # regexps should not contain PageTemplate command text. i.e:
159
+ # /var \w+/i should be used instead of /[% var %]/
160
+ class SyntaxGlossary
161
+ attr_accessor :directive
162
+
163
+ def initialize(directive, glossary)
164
+ @directive = directive
165
+ @glossary = glossary
166
+ end
167
+
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
175
+ end
176
+ end
177
+
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
194
+ end
195
+ end
196
+
197
+ # This is the regexp we tried using for grabbing an entire line
198
+ # Good for everything but ValueCommand :-/.
199
+ # /(?:^\s*\[%([^\]]+?)%\]\s*$\r?\n?|\[%(.+?)%\])/m,
200
+ DEFAULTGLOSSARY = SyntaxGlossary.new(
201
+ /\[%(.+?)%\]/m,
202
+
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
+ )
209
+
210
+ # Preprocessor is a parent class for preprocessors.
211
+ # A preprocessor is used to process or otherwise prepare output for
212
+ # printing by Valuecommand
213
+ #
214
+ # Preprocessors must be singletons, and take a string to convert in
215
+ # some form.
216
+ class Preprocessor
217
+ end
218
+
219
+ # DefaultPreprocessor is a preprocessor with simple processors.
220
+ class DefaultPreprocessor < Preprocessor
221
+ # Just the string itself.
222
+ def DefaultPreprocessor.process(str)
223
+ str
224
+ end
225
+ # Reverse the string. (Just to demonstrate preprocessor)
226
+ def DefaultPreprocessor.reverse(str)
227
+ str.reverse
228
+ end
229
+ # HTML's escapeURI
230
+ def DefaultPreprocessor.escapeURI(string)
231
+ string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
232
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
233
+ end.tr(' ','+')
234
+ end
235
+ # escape HTML.
236
+ def DefaultPreprocessor.escapeHTML(string)
237
+ str = string.gsub(/&/n, '&amp;')
238
+ str.gsub!(/\"/n, '&quot;')
239
+ str.gsub!(/>/n, '&gt;')
240
+ str.gsub!(/</n, '&lt;')
241
+ str
242
+ end
243
+ end
244
+
245
+ # The big ass parser that does all the dirty work of turning
246
+ # templates into compiled commands.
247
+ #
248
+ # Parser.new() accepts a hash as an argument, and looks for these
249
+ # keys: (with defaults)
250
+ #
251
+ # 'namespace' => A namespace object. (A new namespace)
252
+ # 'glossary' => A SyntaxGlossary object. (a dup of DEFAULTGLOSSARY)
253
+ # 'preprocessor' => The preprocessor. (DefaultPreprocessor)
254
+ # 'default_processor' => The processor. (:process)
255
+ # 'source' => The Source for templates. (FileSource)
256
+ #
257
+ # Once the parser is created, it can compile and parse any number of
258
+ # templates.
259
+ #
260
+ # It can be treated as a one-template item by using
261
+ # Parser#load(template), and calling Parser.output
262
+ #
263
+ # To create separate generated templates from the same engine, use
264
+ # Parser#parse, or Parser#load. (It will still keep the most recent
265
+ # one it's #load'd, but that will not affect previously parsed or
266
+ # loaded)
267
+ class Parser
268
+ attr_reader :preprocessor, :default_processor
269
+ attr_reader :glossary, :namespace, :source
270
+ attr_reader :args, :commands
271
+ # Parser.new() accepts a hash as an argument, and looks for these
272
+ # keys: (with defaults)
273
+ #
274
+ # 'namespace' => A namespace object. (A new namespace)
275
+ # 'glossary' => A SyntaxGlossary object. (a dup of DEFAULTGLOSSARY)
276
+ # 'preprocessor' => The preprocessor. (DefaultPreprocessor)
277
+ # 'default_processor' => The processor. (:process)
278
+ # 'source' => The Source for templates. (FileSource)
279
+ def initialize(args = {})
280
+ @args = args # For sub-commands
281
+ @namespace = args['namespace'] || Namespace.new
282
+ @glossary = args['glossary'] || DEFAULTGLOSSARY.dup
283
+ @preprocessor = args['preprocessor'] || DefaultPreprocessor
284
+ @default_processor = args['default_processor'] || :process
285
+ @source = (args['source'] || FileSource).new(@args)
286
+ @commands = nil
287
+ end
288
+ # Load +name+ from a template, and save it to allow this parser to
289
+ # use it for output.
290
+ def load(name)
291
+ @commands = compile(name)
292
+ end
293
+ # Load +name+ from a template, but do not save it.
294
+ def compile(name)
295
+ body = @source.get(name)
296
+ if body
297
+ parse(body)
298
+ else
299
+ TextCommand.new("[ Template '#{name}' not found ]")
300
+ end
301
+ end
302
+ # Compile a Template (BlockCommand) from a string. Does not save
303
+ # the commands.
304
+ def parse(body)
305
+ rx = @glossary.directive
306
+ stack = [Template.new(self)]
307
+ while (m = rx.match(body))
308
+ pre = m.pre_match
309
+ command = m[1].strip.gsub(/\s+/,' ')
310
+ body = m.post_match
311
+
312
+ # Convert all pre-text to a TextCommand
313
+ if (pre && pre.length > 0)
314
+ stack.last.add TextCommand.new(pre)
315
+ end
316
+
317
+ # If the command at the top of the stack is a 'Stacker',
318
+ # Does this command modify it? If so, just skip back.
319
+ next if stack.last.modifies?(command)
320
+
321
+ # If it closes, we're done changing this. Pop it off the
322
+ # Stack and add it to the one above it.
323
+ if stack.last.closes?(command)
324
+ cmd = stack.pop
325
+ stack.last.add(cmd)
326
+ next
327
+ end
328
+
329
+ # Find what command it is.
330
+ cmd = @glossary.lookup(command)
331
+
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
+ # If it's a stacking command, push it on the stack
340
+ if block.is_a?(StackableCommand)
341
+ stack.push block
342
+ else
343
+ stack.last.add block
344
+ end
345
+ end
346
+ stack.last.add TextCommand.new(body) if body && body.length > 0
347
+ if (stack.length > 1)
348
+ raise ArgumentError, 'Mismatched command closures in template'
349
+ end
350
+ stack[0]
351
+ end
352
+ # Not really of any point, but clears the saved commands.
353
+ def clearCommands
354
+ @commands = nil
355
+ 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
+ # If any commands are loaded and saved, return a string of it.
365
+ def output(namespace=nil)
366
+ return '' unless @commands
367
+ @namespace.parent = namespace if namespace
368
+ str = @commands.output(@namespace)
369
+ @namespace.parent = nil
370
+ str
371
+ end
372
+ end
373
+ end