PageTemplate 1.2.0 → 2.0.0

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.
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