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
data/lib/PageTemplate.rb CHANGED
@@ -1,1013 +1,69 @@
1
1
  #!/usr/local/bin/ruby -w
2
2
 
3
- # Use PageTemplate.rb to create output based on template pages and
4
- # the code of your program. This package is inspired by, but not
5
- # quite like, Perl's HTML::Template package. Its main intent is to
6
- # separate design and code for CGI programs, but it could be useful
7
- # in other contexts as well (Ex: site generation packages).
3
+ # Use PageTemplate.rb to create output based on template pages and the
4
+ # code of your program. This package is inspired by, but not quite
5
+ # like, Perl's HTML::Template package. Its main intent is to separate
6
+ # design and code for CGI programs, but it could be useful in other
7
+ # contexts as well (Ex: site gneration packages).
8
8
  #
9
- # See the README.txt file for the real documentation, such as it is.
9
+ # See the README.txt file for the real documentation, such as it is
10
10
  #
11
- # As a side note: if you are using PageTemplate in your projects, or
12
- # add features to your copy, I'd love to hear about it.
11
+ # As a sidenote: if you are are using PageTemplate in your projects,
12
+ # or add features to your copy, we'd love to hear about it.
13
+ #
14
+ # Project home:
15
+ # http://coolnamehere.com/products/pagetemplate/
16
+ #
17
+ # RubyForge url:
18
+ # http://rubyforge.org/projects/pagetemplate/
13
19
  #
14
20
 
15
21
  ############################################################
16
-
17
- # Allows nested storage of variable names and values.
18
- class Namespace
19
-
20
- def initialize()
21
- @global = {}
22
- @nSpaces = []
23
- end
24
-
25
- # An alias for Namespace#set(key, value)
26
- def []=(key, value)
27
- self.set(key, value)
28
- end
29
-
30
- # An alias for Namespace#get(key)
31
- def [](key)
32
- return self.get(key)
33
- end
34
-
35
- # Saves a variable +key+ as the string +value+ in the global
36
- # namespace.
37
- def set(key, value)
38
- puts "Namespace setting global #{key} to #{value}" if $DEBUG
39
- @global[key] = value
40
- end
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
- def get(key)
54
- value = nil
55
-
56
- @nSpaces.reverse.each do |ns|
57
-
58
- if ns.respond_to?(":#{key}")
59
- value = ns.send(":#{key}")
60
- elsif ns.respond_to?("#{key}")
61
- value = ns.send(key)
62
- elsif ns.respond_to?("[]")
63
- value = ns[key]
64
- end
65
-
66
- break if value
67
- end
68
-
69
- value = @global[key] unless value
70
- puts "Namespace #{key}: #{value}" if $DEBUG
71
-
72
- return value
73
- end
74
-
75
- # A convenience method to test whether a variable has a true
76
- # value. Returns nil if +flag+ is not found in the namespace,
77
- # or +flag+ has a nil value attached to it.
78
- def true?(flag)
79
- value = get(flag)
80
- return nil unless value
81
-
82
- return value
83
- end
84
-
85
- # Nest namespaces by adding the object +namespace+ to the current set
86
- # of names.
87
- def push(namespace)
88
- @nSpaces.push(namespace)
89
- end
90
-
91
- # Un-nest namespaces by removing the most recently pushed object.
92
- #
93
- # Returns nil if there are no more namespaces to pop
94
- def pop
95
- @nSpaces.pop
96
- end
97
-
98
- end
99
-
100
- ############################################################
101
-
102
- # Command classes generate text output based on conditions which vary
103
- # between each class. Command provides an abstract base class to show
104
- # interface.
105
- class Command
106
-
107
- # Subclasses of Command use the output method to generate their text
108
- # output. +namespace+ is a Namespace object, which may be
109
- # required by a particular subclass.
110
- def output(namespace = nil)
111
- raise NotImplementedError,
112
- "output() must be implemented by subclasses"
113
- end
114
-
115
- def to_s
116
- return "#{self.class}"
117
- end
118
- end
119
-
120
- # CommentCommand provides a mechanism for ignoring the contents
121
- # of a block.
122
- class CommentCommand < Command
123
-
124
- def output(namespace = nil)
125
- return nil
126
- end
127
-
128
- end
129
-
130
- # BlockCommand provides a single interface to multiple Command
131
- # objects.
132
- class BlockCommand < Command
133
-
134
- def initialize()
135
- @commandBlock = []
136
- end
137
-
138
- # Return Commands held, as a string
139
- def to_s
140
- str = "BlockCommand: "
141
- if @commandBlock
142
- @commandBlock.each { |c| str << "[" + c.to_s + "]"}
143
- else
144
- str << "Empty"
145
- end
146
-
147
- return str
148
- end
149
-
150
- # Returns the number of Commands held in this BlockCommand
151
- def length
152
- return @commandBlock.length
153
- end
154
-
155
- # Adds +command+ to the end of the BlockCommand's
156
- # chain of Commands.
157
- #
158
- # A TypeError is raised if the object being added is not a ((<Command>)).
159
- def add(command)
160
- if command.is_a?(Command)
161
- @commandBlock << command
162
- else
163
- raise TypeError,
164
- "BlockCommand.add: Attempt to add non-Command object"
165
- end
166
- end
167
-
168
- # Applies Command#output(namespace) to each Command contained in this
169
- # object. The output is returned as a single string. If no output
170
- # is generated, returns nil.
171
- def output(namespace = nil)
172
- text = ""
173
- @commandBlock.each do |x|
174
- cOutput = x.output(namespace)
175
- text += cOutput if cOutput
176
- end
177
-
178
- return text unless text == ""
179
- end
180
- end
181
-
182
- # A very simple Command which outputs a static string of text
183
- class TextCommand < Command
184
-
185
- # Creates a TextCommand object, saving +text+ for future output.
186
- def initialize(text)
187
- @text = text
188
- end
189
-
190
- def to_s
191
- return "#{self.type}: '#{@text}'"
192
- end
193
-
194
- # Returns the string provided during this object's creation.
195
- def output(namespace = nil)
196
- return @text
197
- end
198
- end
199
-
200
- # A Command that is tied to a variable name.
201
- class ValueCommand < Command
202
-
203
- # Creates the ValueCommand, with +value+ as the name of the variable
204
- # to be inserted during output.
205
- def initialize(value)
206
- @value = value
207
- end
208
-
209
- # Requests the value of this object's saved value name from
210
- # +namespace+, and returns the string representation of that
211
- # value to the caller.
212
- def output(namespace)
213
- return namespace.get(@value).to_s
214
- end
215
-
216
- def to_s
217
- str = super.to_s
218
- str << "(#{@value})"
219
- return str
220
- end
221
- end
222
-
223
- # The IfCommand generates output from an associated BlockCommand
224
- # if a flag variable is true.
225
- class IfCommand < ValueCommand
226
- # Creates an IfCommand object, with +flag+ used as the value
227
- # to check for truth, and +commandBlock+ as the BlockCommand to
228
- # execute when the flag is true.
229
- def initialize(flag, commandBlock)
230
- super(flag)
231
- @commands = commandBlock
232
- end
233
-
234
- # If +namespace+ has a true value for this Command's flag, returns
235
- # the output generated by the CommandBlock. Otherwise, returns nil.
236
- def output(namespace)
237
- if namespace.true?(@value)
238
- return @commands.output(namespace)
239
- end
240
- end
241
-
242
- # Return a text representation of this command
243
- def to_s
244
- str = super.to_s
245
- str << @commands.to_s
246
- return str
247
- end
248
- end
249
-
250
- # An UnlessCommand simplifies templates that need to use
251
- # a Block only if a condition is false
252
- class UnlessCommand < IfCommand
253
- # If +namespace+ does not have a true value for this Command's flag, returns
254
- # the output generated by the CommandBlock. Otherwise, returns nil.
255
- def output(namespace)
256
- unless namespace.true?(@value)
257
- return @commands.output(namespace)
258
- end
259
- end
260
- end
261
-
262
- # An IfCommand with an alternate BlockCommand to use if the
263
- # flag variable if false.
264
- class IfElseCommand < IfCommand
265
-
266
- # Generates an IfElseCommand with +elseBlock+ as the
267
- # BlockCommand to execute when +flag+ is false.
268
- def initialize(flag, ifBlock, elseBlock)
269
- super(flag, ifBlock)
270
- @elseCommands = elseBlock
271
- end
272
-
273
- # Returns the output of the if BlockCommand if +flag+ is true in
274
- # +namespace+, or the output of the else BlockCommand otherwise.
275
- def output(namespace)
276
- result = super
277
- unless result
278
- result = @elseCommands.output(namespace)
279
- end
280
-
281
- return result
282
- end
283
-
284
- def to_s
285
- str = super.to_s
286
- str << " - ELSE " + @elseCommands.to_s
287
- return str
288
- end
289
- end
290
-
291
- # LoopCommand repeatedly steps through its CommandBlock for each item
292
- # in a array of hashes associated with the LoopCommand's variable.
293
- class LoopCommand < ValueCommand
294
-
295
- # Creates a new LoopCommand asociated with +listName+ and using
296
- # +commandBlock+ as the command to be executed when output is
297
- # requested.
298
- #
299
- # *NOTE:* The value associated with +listName+ in the caller's
300
- # Namespace must be an array of hashes. Each hash should have
301
- # the same keys, but that is not required. (I can think of situations
302
- # where it wouldn't be an issue, so just think of 'identical keys' as a
303
- # guideline)
304
- def initialize(listName, commandBlock)
305
- super(listName)
306
- @commands = commandBlock
307
- end
308
-
309
- # Executes the object's CommandBlock for each item in the associated
310
- # array of hashes. This method nests namespaces, allowing local variables
311
- # within the loop. Returns the combined output of each repetition, or
312
- # nil if the loop value is false in +namespace+.
313
- def output(namespace)
314
-
315
- return nil unless namespace.true?(@value)
316
-
317
- items = namespace.get(@value)
318
-
319
- return nil if items.empty?
320
-
321
- result = ""
322
-
323
- items.each_with_index do |item, index|
324
- metavariables = Namespace.new()
325
- metavariables['__FIRST__'] = true if index == 0
326
- metavariables['__LAST__'] = true if index == items.length - 1
327
- # Even-numbered index means an odd-numbered iteration.
328
- # Gotta love zero-based indexes.
329
- metavariables['__ODD__'] = true if index % 2 == 0
330
-
331
- namespace.push(metavariables)
332
- namespace.push(item)
333
- result += @commands.output(namespace)
334
- namespace.pop()
335
- namespace.pop()
336
- end
337
-
338
- return result
339
- end
340
-
341
- def to_s
342
- str = super.to_s
343
- str << @commands.to_s
344
- return str
345
- end
346
-
347
- end
348
-
349
- # A LoopCommand with an alternate CommandBlock to execute
350
- # if there is no loop associated with the Command variable does not exist.
351
- class LoopElseCommand < LoopCommand
352
-
353
- # Generates a LoopElseCommand with a loop variable, a BlockCommand to
354
- # execute if the loop exists, and a BlockCommand to execute if the
355
- # loop does not exist.
356
- def initialize(loopName, trueBlock, elseBlock)
357
- super(loopName, trueBlock)
358
- @elseCommands = elseBlock
359
- end
360
-
361
- # Returns the output of the loop BlockCommand if the loop exists, or the
362
- # output of the else BlockCommand if not.
363
- def output(namespace)
364
- result = super
365
- unless result
366
- result = @elseCommands.output(namespace)
367
- end
368
-
369
- return result
370
- end
371
-
372
- def to_s
373
- str = super.to_s
374
- str << " - NO " + @elseCommands.to_s
375
- return str
376
- end
377
- end
378
-
379
- # This module provides guidelines for the template parser.
380
- module Syntax
381
-
382
- # Glossary holds regular expression patterns associated with markup in a
383
- # PageTemplate document. It enables the PageTemplate to tell the
384
- # difference between regular document content and template markup. It
385
- # also defines the syntax for the available markup directives:
386
- #
387
- # === Syntax::Glossary read accessors
388
- #
389
- # [:directive]
390
- # The regular expression object which describes a chunk of
391
- # markup text.
392
- # [:glossary]
393
- # A hash of regular expression objects, describing parser keys.
394
- class Glossary
395
-
396
- attr_reader :directive, :glossary
397
-
398
- # Creates a new Syntax::Glossary with regular expression +directive+,
399
- # and hash of regular expressions glossary, as defined above.
400
- #
401
- # +directive+ must have a single grouping, which holds the actual
402
- # directive that is supposed to be processed (as opposed to the
403
- # markers that "wrap" the directive)
404
- #
405
- # The following keys are required in glossary by a Syntax::Glossary
406
- # object:
407
- #
408
- # * value
409
- # * ifopen
410
- # * ifclose
411
- # * ifbranch
412
- # * unlessopen
413
- # * unlessclose
414
- # * loopopen
415
- # * loopclose
416
- # * loopbranch
417
- #
418
- # An ArgumentError is raised if glossary is missing any of these keys.
419
- #
420
- # You may add keys, but it is up to you to make the PageTemplate parse
421
- # method understand their meaning.
422
- def initialize(directive, glossary)
423
- @directive = directive
424
- checkGlossary(glossary)
425
- @glossary = glossary
426
- end
427
-
428
- # Find out which parser directive corresponds to +text+.
429
- # +text+ is the grouping found by a directive match.
430
- #
431
- # If no match is found, returns nil.
432
- # If a match is found, returns an array of the parse command and
433
- # variable associated with it (or the parse command and nil for the
434
- # variable if there is no variable match).
435
- def lookup(text)
436
- command = nil
437
- variable = nil
438
- @glossary.each_key do |key|
439
- if match = @glossary[key].match(text)
440
- command = key
441
- variable = match[1] if match[1]
442
- break
443
- end
444
- end
445
-
446
- return nil if command == nil
447
- return [command, variable]
448
- end
449
-
450
- # Ensures that glossary contains all of the required keys.
451
- private
452
- def checkGlossary(glossary)
453
- %w[ value
454
- ifopen ifclose ifbranch
455
- unlessopen unlessclose
456
- loopopen loopclose loopbranch
457
- ].each do |key|
458
- unless glossary.has_key?(key)
459
- raise ArgumentError,
460
- "Syntax::Glossary:missing '#{key}' key"
461
- end
462
- end
463
-
464
- return true
465
- end
466
- end
467
-
468
- # This Syntax::Glossary is provided for those of us who
469
- # don't feel like defining our own syntax.
470
-
471
- # Here are the keys and patterns associated with Syntax::DEFAULT
472
-
473
- # * directive: /\[%(.+)%\]/
474
- # * comment: /--.+?--/
475
- # * value: /var (\w+)/
476
- # * ifopen: /if (\w+)/
477
- # * ifclose: /endif/
478
- # * ifbranch: /else/
479
- # * unlessopen: /unless (\w+)/
480
- # * unlessclose: /endunless/
481
- # * loopopen: /in (\w+)/
482
- # * loopclose: /endin/
483
- # * loopbranch: /no/
484
- # * include: /include (\S+)/
485
-
486
- # For example +[%var title %]+ is a template directive to
487
- # insert the value of the variable 'title'.
488
-
489
- # Notice that I used a space before the closing braces. This
490
- # Glossary doesn't require it, but I think it makes for a more readable
491
- # style.
492
-
493
- DEFAULT = Syntax::Glossary.new( /\[%(.+?)%\]/,
494
- 'value' => /^\s*var (\w+)\s*$/,
495
- 'comment' => /^\s*--.+?--\s*$/,
496
- 'ifopen' => /^\s*if (\w+)\s*$/,
497
- 'ifclose' => /^\s*endif\s*$/,
498
- 'ifbranch' => /^\s*else\s*$/,
499
- 'unlessopen' => /^\s*unless (\w+)\s*$/,
500
- 'unlessclose'=> /^\s*endunless\s*$/,
501
- 'loopopen' => /^\s*in (\w+)\s*$/,
502
- 'loopclose' => /^\s*endin\s*$/,
503
- 'loopbranch' => /^\s*no\s*$/,
504
- 'include' => /^\s*include (\S+)\s*$/
505
- )
506
-
507
- ########################################################################
508
-
509
- # Uses a Syntax::Glossary to translate a string of text into a
510
- # series of Commands.
511
- class Parser
512
-
513
- attr_reader :commands
514
-
515
- def initialize(glossary, path=Dir.getwd)
516
- @path = path
517
- @syntax = glossary
518
- @commands = nil
519
- @lastFile = nil
520
- @mTime = Time.at(0)
521
- end
522
-
523
- # Parse and compile a file containing PageTemplate directives.
524
- def build(filename)
525
- filename.untaint
526
- if File.exists?(filename)
527
- file = File.new(filename)
528
- else
529
- file = File.new(File.join(@path, filename))
530
- end
531
-
532
- # If this is a new file, or the file has been changed,
533
- # parse and compile.
534
- if filename != @lastFile or file.mtime < @mTime
535
- reset
536
- source = file.readlines
537
- parsed = parse(source, filename)
538
- @commands = compile(parsed, filename)
539
- @lastFile = filename
540
- @mTime = file.mtime
541
- end
542
-
543
- return 1 if @commands
544
- end
545
-
546
- # Returns the result of executing the Parser's internal compiled
547
- # CommandBlock, using ((|namespace||)).
548
- def output(namespace = nil)
549
- return @commands.output(namespace)
550
- end
551
-
552
- # Clear the Parser's CommandBlock.
553
- def reset
554
- @lastFile = nil
555
- @mTime = Time.at(0)
556
- @commands = nil
557
- return 1
558
- end
559
-
560
- # Turn an array of strings +source+ into a series of directives that
561
- # can be handled by Parser#compile.
562
- #
563
- # If provided, +filename+ is assumed to be the name of the
564
- # file which contains the original +source+.
565
- def parse(source, filename = "?")
566
- puts "#{filename}: Parsing ..." if $DEBUG
567
-
568
- # Translate source into listing of text and directives
569
- lineNo = 1
570
- parsed = []
571
- source.each do |line|
572
- # split into plain text and directives
573
- while line
574
- dirMatch = @syntax.directive.match(line)
575
- if dirMatch
576
- matchText = dirMatch[1]
577
- pre = dirMatch.pre_match
578
- directive = @syntax.lookup(matchText)
579
- parsed.push [lineNo, pre]
580
-
581
- if directive
582
- parsed.push [lineNo, directive]
583
- else
584
- # Unrecognized directives are treated as
585
- # plain text.
586
- parsed.push [lineNo, dirMatch[0]]
587
- end
588
-
589
- fragment = dirMatch.post_match
590
- line = fragment
591
- else
592
- parsed.push [lineNo, line]
593
- break
594
- end
595
- end
596
- lineNo += 1
597
- end
598
-
599
- puts "#{filename}: #{@parsed.length} directives" if $DEBUG
600
-
601
- return parsed
602
- end
603
-
604
- # Turn an array of parsed directives into a CommandBlock.
605
- #
606
- # If provided, +filename+ is assumed to be the name of the
607
- # file which contains the original +source+.
608
- def compile(lines, filename = "?")
609
-
610
- puts "#{filename}: Compiling ..." if $DEBUG
611
- # Translate listing of text and directives into a BlockCommand
612
- commands = BlockCommand.new()
613
- max = lines.length
614
- i = 0
615
- while i < max
616
- line = lines[i]
617
- type = line[1].class
618
- command = nil
619
- #puts "#{filename}/#{line[0]}: #{type} -- #{line}"
620
-
621
- if type == String
622
- command = TextCommand.new(line[1])
623
- elsif type == Array
624
- directive = line[1][0]
625
- value = line[1][1]
626
- print "(#{directive} #{value}) " if $DEBUG
627
-
628
- if directive == "value"
629
- command = ValueCommand.new(value)
630
- elsif directive == "include"
631
- command = IncludeCommand.new(value, @path)
632
- elsif directive == "comment"
633
- command = CommentCommand.new()
634
- elsif directive =~ /^(\w+?)open$/
635
- mainBlock = nil
636
- branchBlock = nil
637
- close = nil
638
- branch = nil
639
-
640
- block = $1
641
- startIndex = i + 1
642
- closers = findClose(lines[startIndex..-1],
643
- startIndex, block)
644
-
645
- if closers['close']
646
- close = closers['close']
647
- if closers['branch']
648
- branch = closers['branch']
649
- mainLines = lines[startIndex...branch]
650
- mainBlock = compile(mainLines)
651
- branchLines = lines[branch+1...close]
652
- branchBlock = compile(branchLines)
653
- else
654
- mainLines = lines[startIndex...close]
655
- mainBlock = compile(mainLines)
656
- end
657
- else
658
- raise "#{filename}:#{line[0]} - " +
659
- "#{block} #{value} doesn't close"
660
- end
661
-
662
- if block == "if"
663
- if mainBlock and branchBlock
664
- command = IfElseCommand.new(value,
665
- mainBlock, branchBlock)
666
- elsif mainBlock
667
- command = IfCommand.new(value, mainBlock)
668
- end
669
- elsif block == "unless"
670
- command = UnlessCommand.new(value, mainBlock)
671
- elsif block == "loop"
672
- if mainBlock and branchBlock
673
- command = LoopElseCommand.new(value,
674
- mainBlock, branchBlock)
675
- elsif mainBlock
676
- command = LoopCommand.new(value, mainBlock)
677
- end
678
- else
679
- raise "#{filename}: #{line[0]} - " +
680
- "Unrecognized block type '#{block}'"
681
- end
682
-
683
- # Skip to the line after the block closer.
684
- i = close
685
- elsif %Q[
686
- ifbranch ifclose loopbranch loopclose unlessclose
687
- ].include?(directive)
688
- raise "#{filename}:#{line[0]} - #{directive}" +
689
- " without corresponding opening directive"
690
- else
691
- raise "#{filename}:#{line[0]} - Unknown command #{directive}"
692
- end
693
- else
694
- raise "#{filename}: Unknown instruction in line #{line}"
695
- end
696
-
697
- commands.add(command)
698
- i += 1
699
- end
700
-
701
- # Return compiled BlockCommand
702
- return commands
703
- end
704
-
705
- private
706
- def findClose(lines, mIndex, dType)
707
- branchIndex = nil
708
- closeIndex = nil
709
-
710
- openTerm = dType + "open"
711
- closeTerm = dType + "close"
712
- branchTerm = dType + "branch"
713
-
714
- j = 0
715
- nesting = 0
716
- max = lines.length
717
- while j < max
718
- jLine = lines[j]
719
- jDirective = jLine[1][0]
720
- line = jLine[0]
721
-
722
- if jDirective == openTerm
723
- nesting += 1
724
- elsif jDirective == branchTerm
725
- if nesting == 0
726
- if branchIndex
727
- raise RuntimeError,
728
- "#{@file}:#{line} - Multiple branching statements"
729
- else
730
- branchIndex = j
731
- end
732
- end
733
- elsif jDirective == closeTerm
734
- if nesting > 0
735
- nesting -= 1
736
- else
737
- closeIndex = j
738
- end
739
-
740
- end
741
-
742
- j += 1
743
- end
744
-
745
- branchIndex += mIndex if branchIndex
746
- closeIndex += mIndex if closeIndex
747
-
748
- return { 'branch'=> branchIndex, 'close'=> closeIndex }
749
- end
750
-
751
- end
752
-
753
-
754
- # A Syntax::Parser that can save its compiled CommandBlock for reuse later.
755
- class CachedParser < Parser
756
- def initialize(glossary, path=Dir.getwd)
757
- super(glossary, path)
758
- # Create the cache directory if it does not exist.
759
- end
760
-
761
- def build(filename)
762
- # Determine the name of the cache file.
763
- cacheFile = File.dirname(filename) + "/_" + File.basename(filename)
764
- saveCache = false
765
-
766
- # See if cache exists and is newer than source.
767
- cacheFile.untaint
768
- if File.exists?(cacheFile)
769
- cacheStat = File.stat(cacheFile)
770
- if cacheStat.mtime > File.stat(filename).mtime
771
- # See if cache is newer than internal CommandBlock.
772
- if cacheStat.mtime > @mTime
773
- # Load the cache into memory
774
- @commands = Marshal.load(IO.readlines(cacheFile).join)
775
- @mTime = cacheStat.mtime
776
- elsif cacheStat.mtime < @mTime
777
- saveCache = true
778
- end
779
- else
780
- # Build the CommandBlock
781
- super(filename)
782
- saveCache = true
783
- end
784
- else
785
- super(filename)
786
- saveCache = true
787
- end
788
-
789
- if saveCache
790
- # Write the cache to disk.
791
- data = Marshal.dump(@commands)
792
- f = File.new(cacheFile, "w")
793
- f.write(data)
794
- f.close
795
- end
796
-
797
- return 1 if @commands
798
- end
799
- end
800
- end
801
22
 
802
- ############################################################
23
+ require 'PageTemplate/commands'
24
+ require 'PageTemplate/parser'
803
25
 
804
- # An object which parses text files littered with template directives
805
- # and can produce text based on set parameters and the instructions
806
- # contained within the text file. Each PageTemplate has its own
807
- # Namespace, so there shouldn't be any problems using it in a threaded
808
- # environment.
26
+ # PageTemplate is just the namespace for all of its real code, so as
27
+ # not to caues confusion or clashes with the programmer's code.
809
28
  #
810
- # PageTemplate is the primary user Class for this package.
29
+ # It is a class so that programmers are not confused by being able to
30
+ # do .new on a module
811
31
  class PageTemplate
812
- attr_reader :file, :syntax, :parser
813
- attr_accessor :path
814
-
815
- # Creates a new PageTemplate object, using +args+ to provide
816
- # optional initialization.
817
- #
818
- # ==== Possible Keys For Args
819
- #
820
- # [:include_path]
821
- # Additional directories that PageTemplate will look for template files in.
822
- # - May be a string or Array
823
- #
824
- # [:filename]
825
- # The name of a text file to use for a template.
32
+ # Passes arguments straight to Parser.new
826
33
  #
827
- # [:syntax]
828
- # A Syntax::Glossary object, provided if the user wants
829
- # an alternate syntax for template parse directives. If not
830
- # provided, then Syntax::DEFAULT is used.
831
- #
832
- # [:use_cache]
833
- # If +true+, the PageTemplate object will use a Syntax::CachedParser
834
- # which stores compiled template data to disk in the same directory as the
835
- # template file itself. This cached template still needs you to provide
836
- # a Namespace during output, though.
837
- def initialize(args = {})
838
-
839
- @source = []
840
- @params = Namespace.new()
841
-
842
- if args['syntax']
843
- @syntax = args['syntax']
844
- else
845
- @syntax = Syntax::DEFAULT
846
- end
847
-
848
- @path = [ Dir.getwd() ]
849
- if pathInfo = args['include_path']
850
- if pathInfo.class == Array
851
- @path = @path + pathInfo
852
- else
853
- @path.push(pathInfo)
854
- end
855
- end
856
-
857
- if args['use_cache']
858
- @parser = Syntax::CachedParser.new(@syntax, @path)
859
- else
860
- @parser = Syntax::Parser.new(@syntax, @path)
861
- end
862
-
863
- if args['filename']
864
- @file = args['filename']
865
- load(@file)
866
- else
867
- @file = nil
868
- end
869
-
870
- end
871
-
872
- # Add a path to the search path for loading includes.
873
- def add_path(path)
874
- @path.push(path)
34
+ # returns: a Parser object.
35
+ def PageTemplate.new(*args)
36
+ Parser.new(*args)
875
37
  end
876
-
877
- # Open +file+ and parse its contents so they will be ready
878
- # for template generation.
879
- def load(file)
880
- clearFile()
881
- if File.exists?(file) then
882
- @file = file
883
- else
884
- filepath = File.join(@path, file)
885
- if File.exists?(filepath) then
886
- @file = filepath
887
- else
888
- raise ArgumentError, "Cannot find #{file} in include path"
889
- end
890
- end
891
- @parser.build(@file)
892
- end
893
-
894
- # Set the namespace's +name+ to +value+.
895
- #
896
- # If you need to reset a flag or loop variable to a false value, the
897
- # easiest way is to do just that: <tt>template.setParameter("myflag", false)</tt>
898
- def setParameter(name, value)
899
- @params.set(name, value)
900
- end
901
-
902
- # Get the value associated with +name+ from the namespace. Returns
903
- # nil if the value is not set.
904
- def getParameter(name)
905
- return @params.get(name)
906
- end
907
-
908
- # An alias for PageTemplate.setParameter(key, value)
909
- def []=(key, value)
910
- setParameter(key, value)
911
- end
912
-
913
- # An alias for PageTemplate.getParameter(key)
914
- def [](key)
915
- return getParameter(key)
916
- end
917
-
918
- # Returns the text result of stepping through the compiled template.
919
- # Returns nil if there is no compiled content (usually indicates
920
- # that a file has not been loaded yet).
921
- #
922
- # *NOTE:* This checks the PageTemplate's namespace each time it
923
- # is called, so you can generate different custom pages without
924
- # reloading the template page.
925
- def output
926
- return @parser.commands.output(@params)
927
- end
928
-
929
- # Remove all template data from the PageTemplate, but leave the
930
- # Namespace intact.
931
- def clearFile
932
- @file = nil
933
- @parser.reset()
934
- end
935
- end
936
-
937
- # IncludeCommand allows including text from external files.
938
- class IncludeCommandError < RuntimeError
939
- # Adding nothing, I just want the class name
940
38
  end
941
39
 
942
- class IncludeCommand < Command
943
-
944
- def initialize(filename, path = nil)
945
- super()
946
- @filename = filename
947
- @path = [ Dir.getwd() ]
948
-
949
- if path then
950
- @path = @path + path
951
- end
952
-
953
- @parser = Syntax::Parser.new(Syntax::DEFAULT, @path)
954
- end
955
-
956
- def output(ns=nil)
957
- # First check to see if the file exists in the current working directory
958
- # or as an absolute filename.
959
- text = ""
960
- begin
961
- file = determine_file(ns)
962
- @parser.build(file)
963
- text = @parser.output(ns)
964
- rescue IncludeCommandError => error
965
- text = error
966
- end
967
- return text
968
- end
40
+ =begin
969
41
 
970
- def to_s
971
- return "IncludeCommand {path: '#{@path}' filename: '#{@filename}'}"
972
- end
42
+ PageTemplate.rb is distributed under the MIT License, which is included
43
+ within this document.
973
44
 
974
- private
975
- def determine_file(ns)
976
- file = nil
45
+ The MIT License
977
46
 
978
- if ns
979
- base = ns.get(@filename) || @filename
980
- else
981
- base = @filename
982
- end
47
+ Copyright (c) 2002-2005 Brian Wisti, Greg Millam
983
48
 
984
- @path.each do |p|
985
- f = File.expand_path(File.join(p, base))
986
- f.untaint
987
- if File.exists?(f)
988
- file = f
989
- break
990
- end
991
- end
49
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
50
+ this software and associated documentation files (the "Software"), to deal in
51
+ the Software without restriction, including without limitation the rights to
52
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
53
+ of the Software, and to permit persons to whom the Software is furnished to do
54
+ so, subject to the following conditions:
992
55
 
993
- unless file
994
- raise IncludeCommandError, "File #{base} not found."
995
- end
56
+ The above copyright notice and this permission notice shall be included in all
57
+ copies or substantial portions of the Software.
996
58
 
997
- return file
998
- end
59
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
60
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
61
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
62
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
63
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
64
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
65
+ SOFTWARE.
999
66
 
1000
- end
1001
- # = License
1002
- #
1003
- # ==The MIT License
1004
- #
1005
- # Copyright (c) 2002 Brian Wisti
1006
- #
1007
- # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1008
- #
1009
- # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1010
- #
1011
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1012
- #
1013
- # Brian Wisti (brian@coolnamehere.com)
67
+ Brian Wisti (brian@coolnamehere.com)
68
+ Greg Millam (walker@rubyforge.org)
69
+ =end