amrita2 1.9.6 → 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 (112) hide show
  1. data/README +112 -0
  2. data/init.rb +6 -0
  3. data/lib/amrita2/gettext.rb +116 -0
  4. data/lib/amrita2/macro.rb +153 -0
  5. data/lib/amrita2/rails_bridge.rb +172 -26
  6. data/lib/amrita2/template.rb +2634 -234
  7. data/lib/amrita2/testsupport.rb +171 -0
  8. data/lib/amrita2/version.rb +3 -3
  9. data/lib/amrita2.rb +1 -0
  10. data/sample/depot/app/controllers/admin_controller.rb +59 -0
  11. data/sample/depot/app/controllers/application.rb +20 -0
  12. data/sample/depot/app/controllers/info_controller.rb +19 -0
  13. data/sample/depot/app/controllers/login_controller.rb +85 -0
  14. data/sample/depot/app/controllers/store_controller.rb +68 -0
  15. data/sample/depot/app/helpers/admin_helper.rb +7 -0
  16. data/sample/depot/app/helpers/application_helper.rb +10 -0
  17. data/sample/depot/app/helpers/ar_form.rb +169 -0
  18. data/sample/depot/app/helpers/form_tag.rb +24 -0
  19. data/sample/depot/app/helpers/info_helper.rb +7 -0
  20. data/sample/depot/app/helpers/standard_form.rb +73 -0
  21. data/sample/depot/app/helpers/store_helper.rb +14 -0
  22. data/sample/depot/app/models/cart.rb +36 -0
  23. data/sample/depot/app/models/cart_item.rb +26 -0
  24. data/sample/depot/app/models/line_item.rb +34 -0
  25. data/sample/depot/app/models/order.rb +57 -0
  26. data/sample/depot/app/models/product.rb +41 -0
  27. data/sample/depot/app/models/user.rb +83 -0
  28. data/sample/depot/config/boot.rb +49 -0
  29. data/sample/depot/config/environment.rb +83 -0
  30. data/sample/depot/config/environments/development.rb +24 -0
  31. data/sample/depot/config/environments/production.rb +24 -0
  32. data/sample/depot/config/environments/test.rb +24 -0
  33. data/sample/depot/config/routes.rb +10 -0
  34. data/sample/depot/db/migrate/001_create_products.rb +18 -0
  35. data/sample/depot/db/migrate/002_add_price.rb +14 -0
  36. data/sample/depot/db/migrate/003_add_test_data.rb +68 -0
  37. data/sample/depot/db/migrate/004_add_sessions.rb +20 -0
  38. data/sample/depot/db/migrate/005_create_orders.rb +21 -0
  39. data/sample/depot/db/migrate/006_create_line_items.rb +27 -0
  40. data/sample/depot/db/migrate/007_create_users.rb +18 -0
  41. data/sample/depot/db/schema.rb +45 -0
  42. data/sample/depot/public/dispatch.rb +15 -0
  43. data/sample/depot/test/functional/admin_controller_test.rb +54 -0
  44. data/sample/depot/test/functional/info_controller_test.rb +23 -0
  45. data/sample/depot/test/functional/login_controller_test.rb +74 -0
  46. data/sample/depot/test/functional/store_controller_test.rb +57 -0
  47. data/sample/depot/test/integration/dsl_user_stories_test.rb +126 -0
  48. data/sample/depot/test/integration/user_stories_test.rb +70 -0
  49. data/sample/depot/test/performance/order_speed_test.rb +58 -0
  50. data/sample/depot/test/test_helper.rb +16 -0
  51. data/sample/depot/test/unit/cart_test.rb +39 -0
  52. data/sample/depot/test/unit/cart_test1.rb +31 -0
  53. data/sample/depot/test/unit/line_item_test.rb +15 -0
  54. data/sample/depot/test/unit/order_test.rb +15 -0
  55. data/sample/depot/test/unit/product_test.rb +98 -0
  56. data/sample/depot/vendor/plugins/amrita2/init.rb +6 -0
  57. data/sample/hello/hello.rb +22 -0
  58. data/sample/login_engine/app/controllers/application.rb +16 -0
  59. data/sample/login_engine/app/controllers/user_controller.rb +265 -0
  60. data/sample/login_engine/app/helpers/application_helper.rb +3 -0
  61. data/sample/login_engine/app/helpers/form_tag.rb +16 -0
  62. data/sample/login_engine/app/helpers/two_columns.rb +24 -0
  63. data/sample/login_engine/app/helpers/two_columns_form.rb +47 -0
  64. data/sample/login_engine/app/helpers/user_helper.rb +88 -0
  65. data/sample/login_engine/app/models/user.rb +7 -0
  66. data/sample/login_engine/app/models/user_notify.rb +75 -0
  67. data/sample/login_engine/config/boot.rb +45 -0
  68. data/sample/login_engine/config/environment.rb +140 -0
  69. data/sample/login_engine/config/environments/development.rb +21 -0
  70. data/sample/login_engine/config/environments/production.rb +18 -0
  71. data/sample/login_engine/config/environments/test.rb +19 -0
  72. data/sample/login_engine/config/routes.rb +23 -0
  73. data/sample/login_engine/db/migrate/001_create_users.rb +25 -0
  74. data/sample/login_engine/db/schema.rb +25 -0
  75. data/sample/login_engine/lib/config.rb +113 -0
  76. data/sample/login_engine/lib/hpricot_test_extension.rb +80 -0
  77. data/sample/login_engine/lib/login_engine/authenticated_system.rb +113 -0
  78. data/sample/login_engine/lib/login_engine/authenticated_user.rb +155 -0
  79. data/sample/login_engine/lib/login_engine.rb +62 -0
  80. data/sample/login_engine/public/dispatch.rb +10 -0
  81. data/sample/login_engine/test/functional/amrita2_test.rb +267 -0
  82. data/sample/login_engine/test/functional/user_controller_test.rb +544 -0
  83. data/sample/login_engine/test/mocks/mail.rb +14 -0
  84. data/sample/login_engine/test/mocks/time.rb +19 -0
  85. data/sample/login_engine/test/test_helper.rb +31 -0
  86. data/sample/login_engine/test/unit/user_test.rb +116 -0
  87. data/sample/login_engine/vendor/plugins/amrita2/init.rb +6 -0
  88. data/specs/attribute.rb +201 -0
  89. data/specs/datatypes.rb +231 -0
  90. data/specs/dictionary.rb +68 -0
  91. data/specs/erb_cdata.rb +187 -0
  92. data/specs/filters.rb +513 -0
  93. data/specs/gettext/erb_gettext.rb +42 -0
  94. data/specs/gettext/gettext_util.rb +65 -0
  95. data/specs/gettext/static_text.rb +103 -0
  96. data/specs/impl/code_generator.rb +87 -0
  97. data/specs/impl/dynamic_element.rb +92 -0
  98. data/specs/impl/hash_delegator.rb +57 -0
  99. data/specs/impl/parse_opt.rb +34 -0
  100. data/specs/impl/preprocess.rb +823 -0
  101. data/specs/impl/testsupport.rb +89 -0
  102. data/specs/inlineruby.rb +429 -0
  103. data/specs/intro.rb +654 -0
  104. data/specs/loop.rb +203 -0
  105. data/specs/macro.rb +532 -0
  106. data/specs/sample.rb +34 -0
  107. data/specs/sanitize.rb +110 -0
  108. data/specs/template.rb +189 -0
  109. data/specs/trace.rb +97 -0
  110. metadata +138 -19
  111. data/lib/amrita2/core.rb +0 -1897
  112. data/lib/amrita2/rd.rb +0 -314
@@ -1,304 +1,2704 @@
1
- require 'amrita2/core'
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+ require 'erb'
4
+ #require 'rexml/document'
5
+ require 'enumerator'
6
+
7
+
8
+ module Amrita2
9
+ module ScalarData
10
+ def amrita_value
11
+ Amrita2::Util::sanitize_text(to_s)
12
+ end
13
+ end
14
+
15
+ module DictionaryData
16
+ def amrita_value(name)
17
+ self.__send__(name)
18
+ end
19
+
20
+ def self.===(other)
21
+ if other == nil
22
+ true
23
+ else
24
+ super
25
+ end
26
+ end
27
+ end
28
+
29
+ module Enum
30
+ end
31
+
32
+ module NullObject
33
+ def amrita_value(key=nil)
34
+ nil
35
+ end
36
+
37
+ def each
38
+ end
39
+ end
40
+
41
+ module ElementHelper
42
+ end
43
+ end
44
+
45
+ class NilClass
46
+ include Amrita2::NullObject
47
+ end
48
+
49
+ class FalseClass
50
+ include Amrita2::NullObject
51
+ end
52
+
53
+ class String
54
+ include Amrita2::ScalarData
55
+ def amrita_value
56
+ Amrita2::Util::sanitize_text(self)
57
+ end
58
+ end
59
+
60
+ class Integer
61
+ include Amrita2::ScalarData
62
+ end
63
+
64
+ class BicDecimal
65
+ include Amrita2::ScalarData
66
+ end
67
+
68
+ class Float
69
+ include Amrita2::ScalarData
70
+ end
71
+
72
+ class Time
73
+ include Amrita2::ScalarData
74
+ end
75
+
76
+ # class REXML::Element
77
+ # include Amrita2::ScalarData
78
+ # def amrita_value
79
+ # Amrita2::SanitizedString[to_s]
80
+ # end
81
+
82
+ # def as_amrita_dictionary(*args)
83
+ # Amrita2::Util::REXMLConverter.new(*args).convert(self)
84
+ # end
85
+ # end
86
+
87
+
88
+ class Array
89
+ include Amrita2::Enum
90
+ end
91
+
92
+ class Range
93
+ include Amrita2::Enum
94
+ end
95
+
96
+ class Hash
97
+ include Amrita2::DictionaryData
98
+ def amrita_value(name)
99
+ self[name]
100
+ end
101
+ end
102
+
103
+ class Struct
104
+ include Amrita2::DictionaryData
105
+ end
106
+
107
+ class Binding
108
+ include Amrita2::DictionaryData
109
+
110
+ def amrita_value(name)
111
+ eval "begin; #{name}; rescue(NameError); @#{name};end;", self
112
+ end
113
+ end
114
+
115
+ class Hpricot::Elem
116
+ include Amrita2::ElementHelper
117
+ end
2
118
 
3
119
  module Amrita2
4
- class Template
120
+ FilterMethods = [
121
+ :filter_element,
122
+ :parse_element,
123
+ :parse_node,
124
+ :setup_type_renderer,
125
+ :generate_dynamic_element,
126
+ :define_element_method,
127
+ :loop_check_code,
128
+ :method_body_code,
129
+ :value_filter_code,
130
+ :renderer_code,
131
+ :element_render_code
132
+ ]
133
+
134
+ module Core
135
+ end
136
+
137
+ module Util
138
+ end
139
+
140
+ module Runtime
5
141
  include Amrita2
142
+ include Util
143
+ end
144
+
145
+ module Filters
146
+ class Base
147
+ end
148
+ end
149
+
150
+ module Renderers #:nodoc: all
151
+ class Base
152
+ end
153
+ end
154
+
155
+ module Macro #:nodoc: all
156
+ module Standard
157
+ end
158
+ include Standard
159
+
160
+ class Base
161
+ end
162
+ end
163
+
164
+ module CompileTimeContext #:nodoc: all
165
+ include Filters
166
+ include Renderers
167
+ include Macro
168
+ include Standard
169
+ end
170
+
171
+ class TemplateError < RuntimeError
172
+ end
173
+
174
+ module Util # :nodoc: all
175
+
176
+ # Amrita2 sanitize anything except for SanitizedString
177
+ # If you want to sanitize yourself and don't want to Amrita2 sanitize your object,
178
+ # pass SanitizedString[x] as model data.
179
+ class SanitizedString < String
180
+ def SanitizedString::[](s)
181
+ new(s.to_s).freeze
182
+ end
183
+
184
+ def amrita_value
185
+ self
186
+ end
187
+
188
+ def to_s
189
+ self
190
+ end
191
+
192
+ def *(n)
193
+ SanitizedString[super]
194
+ end
195
+
196
+ def inspect
197
+ %[SanitizedString[#{super}]]
198
+ end
199
+ end
200
+
201
+ # This module provide methods for avoid XSS vulnerability
202
+ # taken from IPA home page(Japanese)
203
+ # http://www.ipa.go.jp/security/awareness/vendor/programming/a01_02.html
204
+ NAMECHAR = '[-\w\d\.:]'
205
+ NAME = "([\\w:]#{NAMECHAR}*)"
206
+ NOT_REFERENCE = "(?!#{NAME};|&#\\d+;|&#x[0-9a-fA-F]+;)" # borrowed from rexml
207
+ AMP_WITHOUT_REFRENCE = /&#{NOT_REFERENCE}/
208
+ # escape &<>
209
+
210
+ def self.sanitize_text(text)
211
+ return nil unless text
212
+ s = text.dup
213
+ s.gsub!(AMP_WITHOUT_REFRENCE, '&amp;')
214
+ s.gsub!("<", '&lt;')
215
+ s.gsub!(">", '&gt;')
216
+ s
217
+ end
218
+
219
+ # escape &<>"'
220
+ def self.sanitize_attribute_value(text)
221
+ return nil unless text
222
+ s = text.dup
223
+ s.gsub!(AMP_WITHOUT_REFRENCE, '&amp;')
224
+ s.gsub!("<", '&lt;')
225
+ s.gsub!(">", '&gt;')
226
+ s.gsub!('"', '&quot;')
227
+ #s.gsub!("'", '&#39;')
228
+ s
229
+ end
230
+
231
+ DefaultAllowedScheme = {
232
+ 'http' => true,
233
+ 'https' => true,
234
+ 'ftp' => true,
235
+ 'mailto' => true,
236
+ }
237
+
238
+ #UrlInvalidChar = Regexp.new(%q|[^;/?:@&=+$,A-Za-z0-9\-_.!~*'()%]|)
239
+ UrlInvalidChar = Regexp.new(%q|[^;/?:@&=+$,A-Za-z0-9\-_.!~*'()%#]|) #'
240
+
241
+ # +sanitize_url+ accepts only these characters
242
+ # --- http://www.ietf.org/rfc/rfc2396.txt ---
243
+ # uric = reserved | unreserved | escaped
244
+ # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
245
+ # unreserved = alphanum | mark
246
+ # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
247
+ # escaped = "%" hex hex
248
+ #
249
+ # +sanitize_url+ accepts only schems specified by +allowd_scheme+
250
+ #
251
+ # The default is http: https: ftp: mailt:
252
+
253
+ def self.sanitize_url(text, allowd_scheme = DefaultAllowedScheme)
254
+ # return nil if text has characters not allowd for URL
255
+
256
+ return nil if text =~ UrlInvalidChar
257
+
258
+ # return '' if text has an unknown scheme
259
+ # --- http://www.ietf.org/rfc/rfc2396.txt ---
260
+ # scheme = alpha *( alpha | digit | "+" | "-" | "." )
261
+
262
+ if text =~ %r|^([A-Za-z][A-Za-z0-9+\-.]*):|
263
+ return nil unless allowd_scheme[$1]
264
+ end
265
+
266
+ # escape HTML
267
+ # special = "&" | "<" | ">" | '"' | "'"
268
+ # But I checked "<" | ">" | '"' before.
269
+ s = text.dup
270
+ #s.gsub!("&", '&amp;')
271
+ s.gsub!("'", '&#39;')
272
+
273
+ s
274
+ end
275
+
276
+ module OptionSupport # :nodoc: all
277
+ attr_reader :opt
278
+ def parse_opt(*args)
279
+ @opt = {}
280
+ hash = args.last
281
+ if hash.kind_of?(Hash)
282
+ @opt.merge!(hash)
283
+ args.pop
284
+ end
285
+
286
+ args.each do |key|
287
+ @opt[key] = key
288
+ end
289
+ @opt
290
+ end
291
+
292
+ def [](key)
293
+ @opt[key]
294
+ end
295
+
296
+ def method_missing(sym, *args, &block)
297
+ if @opt and @opt.has_key?(sym)
298
+ @opt[sym]
299
+ else
300
+ super
301
+ end
302
+ end
303
+ end
304
+ class Option
305
+ include OptionSupport
306
+ def initialize(*args)
307
+ parse_opt(*args)
308
+ end
309
+ end
310
+ class Tuple < Array # :nodoc: all
311
+ include ScalarData
312
+ def self.[](*args)
313
+ self.new(args)
314
+ end
315
+ end
316
+
317
+ class REXMLConverter # :nodoc: all
318
+ include Amrita2
319
+ include Util::OptionSupport
320
+
321
+ def initialize(*args)
322
+ parse_opt(*args)
323
+ end
324
+
325
+ def convert(e)
326
+ @noattrs = select_elements(e, :noattrs)
327
+ @use_child_contents = select_elements(e, :use_child_contents)
328
+ @use_child = select_elements(e, :use_child)
329
+ convert1(e)
330
+ end
331
+
332
+ def convert1(e)
333
+ h = {}
334
+ if use_attr?(e)
335
+ e.attributes.each do |key, value|
336
+ h[key.intern] = value
337
+ end
338
+ end
339
+
340
+ ctag = get_contents_tag(e)
341
+ if ctag
342
+ if e.elements.size > 0
343
+ s = e.collect { |cc| cc.to_s }
344
+ h[ctag] = SanitizedString[s]
345
+ else
346
+ h[ctag] = e.text
347
+ end
348
+ end
349
+
350
+ e.each_element do |c|
351
+ if use_child_contents?(c)
352
+ if c.elements.size > 0
353
+ s = c.collect { |cc| cc.to_s }
354
+ h[c.name.intern] = SanitizedString[s]
355
+ else
356
+ h[c.name.intern] = c.text
357
+ end
358
+ end
359
+
360
+ if use_child?(c)
361
+ ch = convert1(c)
362
+ case h[c.name.intern]
363
+ when nil
364
+ h.merge!(ch)
365
+ when Array
366
+ h[c.name.intern] << ch[c.name.intern]
367
+ else
368
+ h[c.name.intern] = [h[c.name.intern], ch[c.name.intern]]
369
+ end
370
+ end
371
+ end
372
+ if @opt[:use_tag]
373
+ { e.name.intern => h }
374
+ else
375
+ h
376
+ end
377
+ end
378
+
379
+ private
380
+
381
+ def select_elements(e, key)
382
+ case xpath = opt[key]
383
+ when String
384
+ REXML::XPath.match(e, xpath)
385
+ when nil,false
386
+ return []
387
+ else
388
+ REXML::XPath.match(e, "//*")
389
+ end
390
+ end
391
+
392
+ def get_contents_tag(e)
393
+ case o = opt[:use_contents]
394
+ when true, :use_contents
395
+ :contents
396
+ when Symbol
397
+ o
398
+ when nil
399
+ nil
400
+ else
401
+ raise "not implemented"
402
+ end
403
+ end
404
+
405
+ def use_attr?(e)
406
+ not @noattrs.include?(e)
407
+ end
408
+
409
+ def use_child_contents?(e)
410
+ @use_child_contents.include?(e)
411
+ end
412
+
413
+ def use_child?(e)
414
+ @use_child.include?(e)
415
+ end
416
+ end
417
+ end
418
+
419
+ module Runtime # :nodoc: all
420
+ def amrita_set_context_value(v)
421
+ Thread::current[:amrita_context_value] = v
422
+ end
423
+
424
+ def amrita_get_context_value
425
+ Thread::current[:amrita_context_value]
426
+ end
427
+
428
+ def new_element(tag, attrs={}, &block)
429
+ a = {}
430
+ attrs.each { |k,v| a[k.to_s] = v }
431
+ ret = Hpricot::Elem.new(Hpricot::STag.new(tag.to_s, a))
432
+ ret.instance_eval(&block) if block_given?
433
+ ret
434
+ end
435
+
436
+ def start_tag(e, out="")
437
+ out << "<#{e.stag.name}"
438
+ e.attributes.each do |k, v|
439
+ next unless v
440
+ out << " "
441
+ out << "#{k}=\"#{Util::sanitize_attribute_value(v.to_s)}\""
442
+ #attr.write(out)
443
+ end unless e.attributes.empty?
444
+ out << ">"
445
+ out
446
+ end
447
+
448
+ def end_tag(e, out="")
449
+ out << "</#{e.stag.name}>"
450
+ out
451
+ end
452
+ end
453
+
454
+ module ElementHelper # :nodoc: all
455
+ def elements
456
+ children.select do |c|
457
+ c.kind_of?(Hpricot::Elem)
458
+ end
459
+ end
460
+
461
+ def contents
462
+ if children.size > 0
463
+ SanitizedString[children.to_s]
464
+ else
465
+ nil
466
+ end
467
+ end
468
+
469
+ def set_attribute(key, val)
470
+ case val
471
+ when nil, ""
472
+ delete_attribute(key)
473
+ else
474
+ super
475
+ end
476
+ end
477
+
478
+ def delete_attribute(key)
479
+ raw_attributes.delete(key)
480
+ end
481
+
482
+ def add(elem)
483
+ insert_after(elem, nil)
484
+ end
485
+
486
+ def add_text(text)
487
+ insert_after(Hpricot::Text.new(text), nil)
488
+ end
489
+
490
+ def as_amrita_dictionary(opt={})
491
+ ret = {}
492
+ attributes.each do |k, v|
493
+ ret[k.intern] = v
494
+ end
495
+ if opt[:use_contents]
496
+ ret[opt[:use_contents]] =
497
+ Amrita2::SanitizedString[
498
+ children.collect do |c|
499
+ c.to_s
500
+ end.join
501
+ ]
502
+ end
503
+
504
+ if opt[:use_tag]
505
+ { name.intern => ret }
506
+ else
507
+ ret
508
+ end
509
+ end
510
+ end
511
+
512
+ module Core
513
+ class CodeGenerator # :nodoc: all
514
+ def initialize
515
+ @iehack = true
516
+ @lines = []
517
+ @indent = 0
518
+ init_strbuf
519
+ @module_stack = [CGModule.new('')]
520
+ @current_stream = "__stream__"
521
+ end
522
+
523
+ def init_strbuf
524
+ @strbuf = ""
525
+ end
526
+
527
+ def code(line)
528
+ flush
529
+ @lines << [@indent, line]
530
+ end
531
+
532
+ def comment(comments)
533
+ comments.each do |c|
534
+ @lines << [@indent, "# #{c}"]
535
+ end
536
+ end
537
+
538
+ def result
539
+ flush
540
+ m = @module_stack.shift
541
+ if m
542
+ m.constants.each do |name, defs|
543
+ code("#{name} = #{defs}")
544
+ end
545
+ end
546
+ @lines.collect do |indent, l|
547
+ " " * indent + l
548
+ end.join("\n")
549
+ end
550
+
551
+ def put_constant(c)
552
+ @strbuf.concat c.to_s
553
+ end
554
+
555
+ def flush
556
+ if @strbuf.size > 0
557
+ @lines << [@indent, "#{@current_stream}.concat(#{@strbuf.inspect})"]
558
+ init_strbuf
559
+ end
560
+ end
561
+
562
+ def put_hpricot_node(node)
563
+ s = ""
564
+ node.output(s)
565
+ put_constant s
566
+ end
567
+
568
+ def level_up
569
+ @indent += 1
570
+ yield
571
+ flush
572
+ @indent -= 1
573
+ end
574
+
575
+ def if_(cond, &block)
576
+ code("if #{cond}")
577
+ level_up do
578
+ yield
579
+ end
580
+ code("end")
581
+ end
582
+
583
+ def case_(obj, &block)
584
+ code("case #{obj}")
585
+ yield
586
+ code("end")
587
+ end
588
+
589
+ def when_(obj, &block)
590
+ code("when #{obj}")
591
+ level_up do
592
+ yield
593
+ end
594
+ end
595
+
596
+ def else_(&block)
597
+ code("else")
598
+ level_up do
599
+ yield
600
+ end
601
+ end
602
+
603
+ def with_stream(new_stream_name=nil)
604
+ if new_stream_name
605
+ old_stream = @current_stream
606
+ code("before_#{new_stream_name} = #@current_stream")
607
+ @current_stream = new_stream_name.to_s
608
+ end
609
+ code("#@current_stream = '' ")
610
+ yield
611
+ flush
612
+ ensure
613
+ if new_stream_name
614
+ @current_stream = old_stream
615
+ code %[Thread::current[:amrita_stream] ||= {} ]
616
+ code %[Thread::current[:amrita_stream][#{new_stream_name.inspect}] = #{new_stream_name}]
617
+ code("#@current_stream = before_#{new_stream_name}")
618
+ end
619
+ code("#@current_stream")
620
+ end
621
+
622
+ def put_stream(name)
623
+ code %[#@current_stream.concat(Thread::current[:amrita_stream][#{name.inspect}])]
624
+ end
625
+
626
+ def def_method(name, *args)
627
+ if args.size > 0
628
+ code("def #{name}(#{args.join(',')})")
629
+ else
630
+ code("def #{name}")
631
+ end
632
+ level_up do
633
+ with_stream do
634
+ yield
635
+ flush
636
+ end
637
+ end
638
+ code("end")
639
+ end
640
+
641
+ def put_static_element(e)
642
+ attr = e.attributes.collect do |name, value|
643
+ "#{name} = #{value.inspect}"
644
+ end
645
+ if attr.size > 0
646
+ put_constant("<#{e.name} #{attr.join(' ')}>")
647
+ else
648
+ put_constant("<#{e.name}>")
649
+ end
650
+ yield
651
+ put_constant("</#{e.name}>")
652
+ end
653
+
654
+ def put_expression(exp)
655
+ code("#{@current_stream}.concat((#{exp}).to_s)")
656
+ end
657
+
658
+ def put_string_expression(exp)
659
+ code("#{@current_stream}.concat(#{exp})")
660
+ end
661
+
662
+ def define_class(name, &block)
663
+ define_module_or_class("class", name, &block)
664
+ end
665
+
666
+ def define_module_or_class(typ, name, &block)
667
+ @module_stack.unshift CGModule.new(name)
668
+ code("#{typ} #{name}")
669
+ level_up do
670
+ yield
671
+ m = @module_stack.shift
672
+ m.end_of_module(self)
673
+ end
674
+ code("end")
675
+ end
676
+
677
+ def define_constant(const_def)
678
+ @module_stack.first.define_constant(const_def)
679
+ end
680
+
681
+ def eval(src)
682
+ code "eval(#{src}, __binding__)"
683
+ end
684
+
685
+ def eval_and_print(src)
686
+ put_string_expression "eval(#{src}, __binding__)"
687
+ end
688
+
689
+ class CGModule # :nodoc:
690
+ attr_reader :name
691
+ attr_reader :constants
692
+ attr_reader :methods
693
+
694
+ def initialize(name)
695
+ @name = name
696
+ @constants = []
697
+ @methods = []
698
+ end
699
+
700
+ def define_constant(const_def)
701
+ name = nil
702
+ @constants.each do |n, cdef|
703
+ if cdef == const_def
704
+ name = n
705
+ break
706
+ end
707
+ end
708
+ unless name
709
+ name = "C#{sprintf("%03d", @constants.size)}"
710
+ @constants << [name, const_def]
711
+ end
712
+ name
713
+ end
714
+
715
+ def define_method(vars, &block)
716
+ name = "m#{sprintf("%03d", @methods.size)}"
717
+ @methods << [name, vars, block]
718
+ name
719
+ end
720
+
721
+ def end_of_module(cg)
722
+ @constants.each do |name, defs|
723
+ cg.code("#{name} = #{defs}")
724
+ end
725
+ @methods.each do |name, vars, body|
726
+ cg.define_method(name, vars) do
727
+ body.call
728
+ end
729
+ end
730
+ end
731
+ end
732
+ end
733
+
734
+ class BaseNode # :nodoc: all
735
+ attr_reader :parent
736
+
737
+ def initialize(parent)
738
+ @parent = parent
739
+ end
740
+
741
+ def render_me(cg)
742
+ end
743
+
744
+ def module_src(cg)
745
+ end
746
+
747
+ def root
748
+ parent.root
749
+ end
750
+
751
+ def parent_de
752
+ parent
753
+ end
754
+
755
+ def dynamic?
756
+ false
757
+ end
758
+
759
+ def has_ruby?
760
+ false
761
+ end
762
+ end
763
+
764
+ class StaticNode < BaseNode # :nodoc: all
765
+ def initialize(parent, node)
766
+ super(parent)
767
+ @node = node
768
+ end
769
+
770
+ def render_me(cg)
771
+ #cg.put_string_expression(cg.define_constant(@node.to_s.inspect))
772
+
773
+ # to keep &nbsp;
774
+ s = ""
775
+ @node.output(s, :preserve=>true)
776
+ cg.put_string_expression(cg.define_constant(s.inspect))
777
+ end
778
+ end
779
+
780
+ class CommentNode < BaseNode # :nodoc: all
781
+ def initialize(parent, node)
782
+ super(parent)
783
+ @node = node
784
+ end
785
+
786
+ def render_me(cg)
787
+ #cg.put_string_expression(cg.define_constant(@node.to_s.inspect))
788
+ # to keep &nbsp;
789
+ s = ""
790
+ @node.output(s, :preserve=>true)
791
+ cg.put_string_expression(cg.define_constant(s.inspect))
792
+ end
793
+ end
794
+
795
+ class ErbNode < BaseNode # :nodoc: all
796
+ include Util
797
+ attr_reader :node
798
+ def initialize(parent, node)
799
+ super(parent)
800
+ @node = node
801
+ @erb_trim_mode = nil
802
+ end
803
+
804
+ def dynamic?
805
+ true
806
+ end
807
+
808
+ def render_me_old(cg)
809
+ src = @node.inner_text
810
+ src.each do |s|
811
+ cg.code("# #{s.chomp}")
812
+ end
813
+ erb = cg.define_constant %[ERB.new(#{with_context_value_erb(src).inspect}, nil, #{@erb_trim_mode.inspect})]
814
+ cg.code("amrita_set_context_value($_)")
815
+ cg.put_string_expression %[#{erb}.result(__binding__)]
816
+ cg.code("$_ = amrita_get_context_value")
817
+ end
818
+
819
+ def render_me(cg)
820
+ src = @node.inner_text
821
+ src.each do |s|
822
+ cg.code("# #{s.chomp}")
823
+ end
824
+ erb = cg.define_constant %[ERB.new(#{with_context_value_erb(src).inspect}, nil, #{@erb_trim_mode.inspect}).src]
825
+ cg.code("amrita_set_context_value($_)")
826
+ cg.eval_and_print(erb)
827
+ cg.code("$_ = amrita_get_context_value")
828
+ end
829
+
830
+ def has_ruby?
831
+ true
832
+ end
833
+
834
+ private
835
+ def with_context_value_erb(src)
836
+ [
837
+ "<% $_ = amrita_get_context_value %>",
838
+ src,
839
+ "<% amrita_set_context_value($_) %>",
840
+ ].join("")
841
+ end
842
+ end
843
+
844
+ class ParentNode < BaseNode # :nodoc: all
845
+ attr_reader :element, :parent, :children
846
+ def initialize(parent, element)
847
+ super(parent)
848
+ @element = element
849
+ @children = parent_de.parse_element(@element)
850
+ end
851
+
852
+ def dynamic?
853
+ children.any? { |c| c.dynamic? }
854
+ end
855
+
856
+ def has_dynamic?
857
+ children.any? { |c| c.dynamic? }
858
+ end
859
+
860
+ def has_ruby?
861
+ children.any? { |c| c.has_ruby? }
862
+ end
863
+
864
+ def each(&block)
865
+ children.each(&block)
866
+ end
867
+
868
+ def module_src(cg)
869
+ each do |c|
870
+ c.module_src(cg)
871
+ end
872
+ end
873
+
874
+ end
875
+
876
+ class CompoundElement < ParentNode # :nodoc: all
877
+ def render_me(cg)
878
+ if @element.empty?
879
+ cg.put_constant(@element.to_s)
880
+ else
881
+ @parent.element_render_code(cg, @element) do
882
+ each do |c|
883
+ c.render_me(cg)
884
+ end
885
+ end
886
+ end
887
+ end
888
+ end
889
+
890
+ class DynamicElement < ParentNode # :nodoc: all
891
+ include Util
892
+ include OptionSupport
893
+
894
+ attr_reader :parent, :name, :children, :filter, :renderers, :element, :attrs
895
+ def self.delegate_to_filter(name)
896
+ module_eval <<-END
897
+ def #{name}(*args,&block)
898
+ @filter.#{name}(self, *args,&block)
899
+ end
900
+ END
901
+ end
902
+
903
+ FilterMethods.each do |m|
904
+ delegate_to_filter(m)
905
+ end
906
+
907
+ def initialize(parent, name, element, filters=[])
908
+ @parent,@name,@element = parent, name, element
909
+ parse_opt(root.opt)
910
+ @attrs = {}
911
+ delete_am_attrs
912
+ setup_filter(filters)
913
+ @element = filter_element(@element)
914
+ @renderers = []
915
+ super(parent, element)
916
+
917
+ setup_type_renderer
918
+ end
919
+
920
+ def [](key)
921
+ @attrs[key]
922
+ end
923
+
924
+ def parent_de
925
+ self
926
+ end
927
+
928
+ def dynamic?
929
+ true
930
+ end
931
+
932
+ def has_ruby?
933
+ super or am_for_value or am_skipif_value or am_v_value
934
+ end
935
+
936
+ def setup_filter(filters)
937
+ #default_filter = @parent ? @parent.filter : DefaultFilter.new
938
+ default_filter = DefaultFilter.new
939
+ default_filter.parse_opt(opt)
940
+ if filters.size > 0
941
+ @filter = current = filters.first
942
+ for f in filters do
943
+ current.next_ = f
944
+ current = f
945
+ end
946
+ current.next_ = default_filter
947
+ else
948
+ @filter = default_filter
949
+ end
950
+ end
951
+
952
+ def class_name
953
+ if name
954
+ "XX" + name.capitalize
955
+ else
956
+ "XX%d" % [object_id.abs]
957
+ end
958
+ end
959
+
960
+ def instance_name
961
+ class_name + "Instance"
962
+ end
963
+
964
+ def compile(cg = CodeGenerator.new)
965
+ define_element_method(cg) do
966
+ cg.code("$_ = value")
967
+ loop_check_code(cg)
968
+ method_body_code(cg)
969
+ end
970
+ end
971
+
972
+ def render_me(cg, n = name)
973
+ if (n)
974
+ cg.put_string_expression("#{instance_name}.render_with($_.amrita_value(:#{n}), __binding__)")
975
+ else
976
+ cg.put_string_expression("#{instance_name}.render_with($_, __binding__)")
977
+ end
978
+ end
979
+
980
+ def module_src(cg)
981
+ cg.define_class(class_name) do
982
+ cg.code <<-END
983
+ include Amrita2
984
+ include Amrita2::Runtime
985
+ END
986
+ compile(cg)
987
+ super
988
+ end
989
+ cg.code("#{instance_name} = #{class_name}.new")
990
+ end
991
+
992
+ def am_for_value
993
+ @attrs[am_for.intern]
994
+ end
995
+
996
+ def am_skipif_value
997
+ @attrs[am_skipif.intern]
998
+ end
999
+
1000
+ def am_v_value
1001
+ @attrs[am_v.intern]
1002
+ end
1003
+
1004
+ private
1005
+
1006
+ def delete_am_attrs
1007
+ [
1008
+ am_src,
1009
+ am_filter,
1010
+ am_skipif,
1011
+ am_for,
1012
+ am_v,
1013
+ ].each do |key_s|
1014
+ key = key_s.intern
1015
+ @attrs[key] = @element.attributes[key_s]
1016
+ @element.delete_attribute(key_s)
1017
+ end
1018
+ end
1019
+ end
1020
+
1021
+ class RootElement < DynamicElement # :nodoc: all
1022
+ include Filters
1023
+
1024
+ attr_reader :xml_decl, :doctype
1025
+
1026
+ def initialize(elements, opt={}, &block)
1027
+ @xml_decl = @doctype = nil
1028
+ parse_opt(opt)
1029
+ if block_given?
1030
+ @filter_proc = block
1031
+ else
1032
+ @filter_proc = proc do |e, src, filters|
1033
+ end
1034
+ end
1035
+ e = Hpricot.make("<root />", :xml=>true).first
1036
+ elements.each do |c|
1037
+ case c
1038
+ when Hpricot::Elem, Hpricot::Text, Hpricot::Comment, Hpricot::BogusETag
1039
+ e.insert_after(c, nil)
1040
+ when Hpricot::XMLDecl
1041
+ @xml_decl = c
1042
+ when Hpricot::DocType
1043
+ @doctype = c
1044
+ else
1045
+ raise "#{c.class}"
1046
+ end
1047
+ end
1048
+ filters = []
1049
+ @filter_proc.call(e,nil, filters)
1050
+ super(nil, nil, e, filters)
1051
+ end
1052
+
1053
+ def root
1054
+ self
1055
+ end
1056
+
1057
+ def compile(cg)
1058
+ cg.code <<-END
1059
+ include Amrita2
1060
+ include Amrita2::Runtime
1061
+ END
1062
+ super(cg)
1063
+ @children.each do |c|
1064
+ c.module_src(cg)
1065
+ end
1066
+ end
1067
+
1068
+ def compile_filters(element, name, *args)
1069
+ src = args.compact.join('|')
1070
+ #filters = src.split('|').collect { |f| parse_filter(f) }
1071
+ filters = parse_filter(src)
1072
+ @filter_proc.call(element, name, filters)
1073
+ filters
1074
+ end
1075
+
1076
+ def parse_filter(src)
1077
+ src.gsub!("\n", " ")
1078
+ case src
1079
+ when "",nil
1080
+ []
1081
+ else
1082
+ case ret = eval(src, @opt[:compiletime_binding])
1083
+ when Class
1084
+ [ret.new]
1085
+ when Module
1086
+ [ModuleExtendFilter[ret]]
1087
+ when Filters::Base
1088
+ [ret]
1089
+ when Array
1090
+ case ret.first
1091
+ when Symbol
1092
+ [FunctionFilter[*ret]]
1093
+ else
1094
+ ret
1095
+ end
1096
+ when Symbol
1097
+ [FunctionFilter[ret]]
1098
+ when nil
1099
+ []
1100
+ else
1101
+ raise TemplateError, "unknown Filter type #{ret.class}"
1102
+ end
1103
+ end
1104
+ rescue ScriptError, NameError
1105
+ raise TemplateError, "error in filters #{src.inspect} #$!"
1106
+ end
1107
+ end
1108
+
1109
+ class DefaultFilter < Filters::Base
1110
+ include Renderers
1111
+ include Filters
1112
+ include Util::OptionSupport
1113
+
1114
+ def filter_element(de, element)
1115
+ element
1116
+ end
1117
+
1118
+ def parse_element(de, element)
1119
+ element.enum_for(:each_child).collect do |node|
1120
+ de.parse_node(node)
1121
+ end
1122
+ end
1123
+
1124
+ def parse_node(de, node)
1125
+ case node
1126
+ when Hpricot::Elem
1127
+ de.generate_dynamic_element(node) || CompoundElement.new(de, node)
1128
+ when Hpricot::CData
1129
+ ErbNode.new(de, node)
1130
+ when Hpricot::Text
1131
+ StaticNode.new(de, node)
1132
+ when Hpricot::Comment
1133
+ CommentNode.new(de, node)
1134
+ when Hpricot::BogusETag
1135
+ StaticNode.new(de, node)
1136
+ else
1137
+ raise "not implemented #{node.class}"
1138
+ end
1139
+ end
1140
+
1141
+ def generate_dynamic_element(de, e)
1142
+ src = e.attributes[de.am_src]
1143
+ filters_src = e.attributes[de.am_filter]
1144
+ if src
1145
+ name, fs = src.split('|', 2)
1146
+ name.strip!
1147
+ filters = de.root.compile_filters(e, name, fs, filters_src)
1148
+ DynamicElement.new(de, name , e, filters)
1149
+ elsif filters_src
1150
+ filters = de.root.compile_filters(e, nil, filters_src)
1151
+ DynamicElement.new(de, nil , e, filters)
1152
+ else
1153
+ nil
1154
+ end
1155
+ end
1156
+
1157
+ def setup_type_renderer(de)
1158
+ if de.has_dynamic?
1159
+ de.renderers << HashRenderer.new unless de.renderers.find {|r| r.kind_of?(HashRenderer) }
1160
+ else
1161
+ de.renderers << ScalarRenderer.new
1162
+ end
1163
+ de.renderers << ElseRenderer.new
1164
+ end
1165
+
1166
+ def define_element_method(de, cg, &block)
1167
+ cg.def_method("render_with", "value", "__binding__", "__cnt__ = -1 ", &block)
1168
+ end
1169
+
1170
+ def loop_check_code(de, cg)
1171
+ cg.if_ "$_.kind_of?(Amrita2::Enum) and not $_.kind_of?(ScalarData)" do
1172
+ cg.code("$_.each_with_index do |v, i| ")
1173
+ cg.level_up do
1174
+ cg.put_string_expression("render_with(v, __binding__, i) ")
1175
+ end
1176
+ cg.code("end")
1177
+ cg.code("return __stream__")
1178
+ end
1179
+ end
1180
+
1181
+ def method_body_code(de, cg)
1182
+ de.value_filter_code(cg, "value")
1183
+ #cg.code("p #{de.element.name.inspect}, $_, stream")
1184
+ de.renderer_code(cg, de.element)
1185
+ #cg.code("p #{de.element.name.inspect}, $_, stream")
1186
+ end
1187
+
1188
+ def value_filter_code(de, cg, value_name)
1189
+ end
1190
+
1191
+ def renderer_code(de, cg, element)
1192
+ r = de.renderers
1193
+ case r.size
1194
+ when 0
1195
+ when 1
1196
+ r.first.generate_body(cg, de, element)
1197
+ else
1198
+ cg.case_("$_") do
1199
+ r.each do |r1|
1200
+ r1.generate_when(cg) do
1201
+ r1.generate_body(cg, de, element)
1202
+ end
1203
+ end
1204
+ end
1205
+ end
1206
+ end
1207
+
1208
+ def element_render_code(de, cg, element, &block)
1209
+ if ((element.name == 'span' or element.name == '_' or element.name == 'root') and element.attributes.size == 0)
1210
+ yield
1211
+ else
1212
+ cg.put_static_element(element, &block)
1213
+ end
1214
+ end
1215
+ end
1216
+
1217
+ #
1218
+ # = using Amrita2::Template
1219
+ #
1220
+ # require "amrita2/template"
1221
+ # include Amrita2
1222
+ # tmpl = Template.new <<-END
1223
+ # <<html<
1224
+ # <<body<
1225
+ # <<h1 :title>>
1226
+ # <<p :body<
1227
+ # <<:template>> is a html template libraly for <<:lang>>
1228
+ # END
1229
+ #
1230
+ # puts tmpl.render_with(:title=>"hello world", :body=>{ :template=>"Amrita2", :lang=>"Ruby" })
1231
+
1232
+ class Template
1233
+ include Amrita2
1234
+ include Util
1235
+ include OptionSupport
1236
+ include Filters
1237
+ include CompileTimeContext
1238
+ include Runtime
1239
+
1240
+ # Specify attribute prefix for dynamic element. Defaults to 'am:'.
1241
+ attr_accessor :amrita_prefix
1242
+ # Specify weather use inline ruby. Defaults to +true+
1243
+ attr_accessor :inline_ruby
1244
+
1245
+ attr_reader :tracer
1246
+ attr_reader :root
1247
+
1248
+ # Initialize Template object using +text+ and +opts+.
1249
+ # Optionaly specify block for setting filters.
1250
+ #
1251
+ # t = Amrita2::Template.new(text) do |e, src, filters|
1252
+ # if src == "aaa"
1253
+ # filters << Amrita2::Filters::Default[456]
1254
+ # end
1255
+ # end
1256
+
1257
+ def initialize(text, *opts, &block)
1258
+ parse_opt(*opts)
1259
+ @text = text.dup
1260
+ @setuped = false
1261
+ @filter_proc = block
1262
+ @text_domain = nil
1263
+ @inline_ruby = true
1264
+ @amrita_prefix = @opt[:amrita_prefix] || "am:"
1265
+ end
1266
+
1267
+ def amrita_src=(v)
1268
+ @opt[:amrita_src] = v
1269
+ end
1270
+
1271
+ def amrita_filter=(v)
1272
+ @opt[:amrita_filter] = v
1273
+ end
1274
+
1275
+ def compiletime_binding=(b)
1276
+ @opt[:compiletime_binding] = b
1277
+ end
1278
+
1279
+ # Print compiled ruby code and runtime trace to
1280
+ # +io_or_type+
1281
+ #
1282
+ # tmpl = Template.new "...."
1283
+ # tmpl.set_trace(STDOUT)
1284
+ # puts tmpl.render_with(...)
1285
+
1286
+ def set_trace(io_or_type, &block)
1287
+ @tracer = Tracer.new(io_or_type, &block)
1288
+ end
1289
+
1290
+ def setup
1291
+ setup_opt
1292
+ @preprocessor = PreProcessor.new(@opt)
1293
+ cg = CodeGenerator.new
1294
+ preprocessed_text = @preprocessor.process(@text)
1295
+
1296
+ if bc = @preprocessor.sections[:BeforeCompile]
1297
+ self.instance_eval do
1298
+ eval bc
1299
+ end
1300
+ end
1301
+
1302
+ filter_setup do |e, src, filters|
1303
+ filters.unshift Trace.new if @tracer and @tracer.auto_trace?
1304
+ filters.unshift InlineRuby.new if @inline_ruby
1305
+ end
1306
+
1307
+ compile(cg, preprocessed_text)
1308
+ @setuped = true
1309
+ cg.result
1310
+ ensure
1311
+ @tracer.code(cg) if @tracer and @tracer.code_trace?
1312
+ end
1313
+
1314
+ # render template with +value+ and +binding+.
1315
+ # use TOPLEVEL_BINDING if +binding+ is nil.
1316
+ #
1317
+ def render_with(value, binding_=nil)
1318
+ setup unless @setuped
1319
+ unless binding_
1320
+ if value.kind_of?(Binding)
1321
+ binding_ = value
1322
+ else
1323
+ binding_ = binding
1324
+ end
1325
+ end
1326
+
1327
+ ret = nil
1328
+
1329
+ if @opt[:process_xhtml]
1330
+ ret = ""
1331
+ ret << @root.xml_decl.to_s << "\n" if @root.xml_decl
1332
+ ret << @root.doctype.to_s << "\n" if @root.doctype
1333
+ ret << @cls.new.render_with(value, binding_)
1334
+ else
1335
+ ret = @cls.new.render_with(value, binding_)
1336
+ end
1337
+ SanitizedString[ret]
1338
+ end
1339
+
1340
+ private
1341
+ def setup_opt
1342
+ @opt[:am_src] = @opt[:amrita_src] || @amrita_prefix + "src"
1343
+ @opt[:am_filter] = @opt[:amrita_filter] || @amrita_prefix + "filter"
1344
+ @opt[:am_skipif] = @opt[:amrita_skipif] || @amrita_prefix + "skipif"
1345
+ @opt[:am_for] = @opt[:amrita_for] || @amrita_prefix + "for"
1346
+ @opt[:am_v] = @opt[:amrita_v] || @amrita_prefix + "v"
1347
+ @opt[:tracer] = @tracer
1348
+ @opt[:compiletime_binding] = @opt[:compiletime_binding] || binding
1349
+ end
1350
+
1351
+ def compile(cg, text)
1352
+ @elements = Hpricot.make(text, :xml=>true)
1353
+ @root = RootElement.new @elements, opt, &@filter_proc
1354
+ @root.text_domain = @text_domain if @text_domain
1355
+ @root.compile(cg)
1356
+
1357
+ @cls = Class.new
1358
+
1359
+ ##############################################################################
1360
+ # very dirty hack
1361
+ # ruby-gettext does not work well with class without a name
1362
+ #
1363
+ #const_name = "T%08x" % Thread.current.object_id
1364
+ const_name = "T%08x" % @cls.object_id
1365
+ Amrita2.module_eval { remove_const(const_name) } if Amrita2.const_defined?(const_name)
1366
+ Amrita2.const_set(const_name, @cls)
1367
+ # This should be modified later
1368
+ ##############################################################################
1369
+
1370
+ @cls.module_eval cg.result
1371
+ @cls.const_set(:Tracer, @tracer) if @tracer
1372
+ rescue SyntaxError
1373
+ raise RuntimeError,$!
1374
+ end
1375
+
1376
+ def setup_filter_proc
1377
+ old_proc = @filter_proc
1378
+ @filter_proc = proc do |e, name ,filters|
1379
+ filters.unshift Trace.new if @tracer and @tracer.auto_trace?
1380
+ filters.unshift InlineRuby.new if @inline_ruby
1381
+ old_proc.call(e, name ,filters) if old_proc
1382
+ end
1383
+ end
1384
+
1385
+ # ment to be used in macro.rb and BeforeCompile section
1386
+ def filter_setup(&block)
1387
+ old_proc = @filter_proc
1388
+ @filter_proc = proc do |e, name ,filters|
1389
+ block.call(e, name, filters)
1390
+ old_proc.call(e, name, filters) if old_proc
1391
+ end
1392
+ end
1393
+
1394
+ class Tracer
1395
+ def initialize(io_or_type, &block)
1396
+ @auto_trace = false
1397
+ @trace_proc = block
1398
+ case io_or_type
1399
+ when :code, :element, :all
1400
+ @type = io_or_type
1401
+ raise "need block" unless @trace_proc
1402
+ when :all_element
1403
+ @type = :element
1404
+ @auto_trace = true
1405
+ raise "need block" unless @trace_proc
1406
+ else
1407
+ if io_or_type.respond_to?(:<<)
1408
+ @type = :all
1409
+ @auto_trace = true
1410
+ @trace_proc = proc do |msg|
1411
+ io_or_type << msg << "\n"
1412
+ end
1413
+ else
1414
+ raise "unknown type for trace #{io_or_type.inspect}"
1415
+ end
1416
+ end
1417
+ end
1418
+
1419
+ def auto_trace?
1420
+ @auto_trace
1421
+ end
1422
+
1423
+ def code_trace?
1424
+ @type == :code or @type == :all
1425
+ end
1426
+
1427
+ def code(cg)
1428
+ @trace_proc.call(cg.result) if code_trace?
1429
+ end
1430
+
1431
+ def <<(msg)
1432
+ @trace_proc.call(msg) if @type == :all or @type == :element
1433
+ end
1434
+ end
1435
+ end
1436
+
1437
+ class PreProcessor # :nodoc: all
1438
+ attr_reader :sections
1439
+ CDataStart = %r[(.*?)(<!\[CDATA\[)(.*)]m
1440
+ CDataEnd = %r[(.*?)(\]\]>)(.*)]m
1441
+
1442
+ def initialize(opt={})
1443
+ @opt = opt
1444
+ init()
1445
+ @trace = false
1446
+ #@trace = true
1447
+ end
1448
+
1449
+ def process(s)
1450
+ init
1451
+ ret = process_block(s)
1452
+ ret = process_erb_and_inline_amxml(ret)
1453
+ ret << pop_tag(0)
1454
+ ret
1455
+ end
1456
+
1457
+ def process_erb_and_inline_amxml(s)
1458
+ if s =~ CDataStart
1459
+ out_of_cdata, cdata, in_cdata = $1, $2, $3
1460
+ process_out_of_cdata(out_of_cdata) + cdata + parse_in_cdata(in_cdata)
1461
+ else
1462
+ process_out_of_cdata(s)
1463
+ end
1464
+ end
1465
+
1466
+ def process_block(s)
1467
+ ret = ""
1468
+ lines = s.to_a
1469
+ while line = lines.shift
1470
+ next if line =~ /^\s*$/
1471
+ current_indent = line[/^ */].size
1472
+ ret << pop_tag(current_indent)
1473
+
1474
+ case line
1475
+ when %r[^\s*#]
1476
+ # It's a comment. do nothing
1477
+ when AmXml::R_AMXML_BLOCK_START
1478
+ amxml = $1
1479
+ while true
1480
+ l = lines.shift
1481
+ case l
1482
+ when %r[(.*)<(?: |-)*$]
1483
+ amxml << $1
1484
+ break
1485
+ when nil
1486
+ raise "incomplete amxml block start #{amxml}"
1487
+ else
1488
+ amxml << l
1489
+ end
1490
+ end
1491
+ a = AmXml.parse(amxml, @opt)
1492
+ raise "illeagal amxml multi line #{amxml.inspect}" unless a
1493
+ push_tag(current_indent, a.tag)
1494
+ ret << a.result(false)
1495
+ when AmXml::R_AMXML_ERB_LINE
1496
+ mark, src = $1,$2
1497
+ ret << "<![CDATA[<% "
1498
+ while true
1499
+ case mark
1500
+ when '='
1501
+ ret << "\n _erbout.concat(eval(%{#{src}}).to_s) "
1502
+ when String
1503
+ ret << "\n#{src}"
1504
+ else
1505
+ break
1506
+ end
1507
+ case l = lines.shift
1508
+ when AmXml::R_AMXML_ERB_LINE
1509
+ mark, src = $1,$2
1510
+ else
1511
+ mark = nil
1512
+ lines.unshift l
1513
+ end
1514
+ end
1515
+ ret << "\n%>]]>\n"
1516
+ when AmXml::R_AMXML_BLOCK_LINE
1517
+ a = AmXml.parse($1, {:trline=>$2.include?("-")}.merge(@opt))
1518
+ raise "illeagal amxml block line #{line.inspect}" unless a
1519
+ ret << a.result(false)
1520
+ push_tag(current_indent, a.tag)
1521
+ when %r[(.*)^\s*>>>\s*$]m
1522
+ ret << $1
1523
+ raise "unmatched >>> " unless @stack.size > 0
1524
+ ret << pop_tag(current_indent)
1525
+ when %r[^\s*\|\s*([\w:]*)\s*\|\|]
1526
+ lp = LineProcessor.new do |cell_text|
1527
+ PreProcessor.new.process_block(cell_text)
1528
+ end
1529
+ while lp.parse_line(line)
1530
+ line = lines.shift
1531
+ end
1532
+ lines.unshift(line) unless line =~ /^( |-)*$/
1533
+ ret << lp.get_result_xml
1534
+ ret
1535
+ else
1536
+ ret << line
1537
+ end
1538
+ end
1539
+ ret << pop_tag(0)
1540
+ ret
1541
+ end
1542
+
1543
+ private
1544
+
1545
+ def init
1546
+ @sections = {}
1547
+ @stack = []
1548
+ end
1549
+
1550
+ def process_out_of_cdata(s)
1551
+ erb_nest=0
1552
+ ret = ""
1553
+ while s.size > 0
1554
+ puts "process_out_of_cdata:#{erb_nest}:#{@stack.join('/')}:#{s}" if @trace
1555
+ case s
1556
+ when %r[\A(.*?)(<%|%>)((?=.?))]m
1557
+ case $2
1558
+ when "<%" ; erb_nest +=1
1559
+ when "%>" ; erb_nest -= 1
1560
+ end
1561
+ if $2 == "<%" and erb_nest == 1
1562
+ if Regexp.last_match.post_match[0] == "("[0] and
1563
+ s =~ %r[\A(.*?)(<%\((\w+?)\))]m
1564
+ ret << process_inline_amxml($1)
1565
+ s = process_section($3.intern, Regexp.last_match.post_match, 1)
1566
+ erb_nest -= 1
1567
+ else
1568
+ ret << process_inline_amxml($1) << "<![CDATA[<%"
1569
+ s = Regexp.last_match.post_match
1570
+ end
1571
+ elsif $2 == "%>" and erb_nest == 0
1572
+ ret << $1 << $2 << "]]>"
1573
+ s = Regexp.last_match.post_match
1574
+ else
1575
+ ret << $1 << $2
1576
+ s = Regexp.last_match.post_match
1577
+ end
1578
+ else
1579
+ ret << process_inline_amxml(s)
1580
+ s = ""
1581
+ end
1582
+ end
1583
+ ret
1584
+ end
1585
+
1586
+ def parse_in_cdata(s)
1587
+ if s =~ CDataEnd
1588
+ in_cdata, cdata_end, out_of_cdata = $1, $2, $3
1589
+ in_cdata + cdata_end + process_erb_and_inline_amxml(out_of_cdata)
1590
+ else
1591
+ raise "end of cdata not found in #{s}"
1592
+ end
1593
+ end
1594
+
1595
+ def process_section(section_id, s, nest)
1596
+ puts "%s:%d:%s" % [section_id, nest, s] if @trace
1597
+ @sections[section_id] ||= ""
1598
+ case s
1599
+ when %r[\A(.*?)(<%|%>)(.*)\Z]m
1600
+ case $2
1601
+ when "<%"
1602
+ @sections[section_id] << $1 << $2
1603
+ s = process_section(section_id, $3, nest+1)
1604
+ when "%>"
1605
+ @sections[section_id] << $1
1606
+ if nest == 1
1607
+ $3
1608
+ else
1609
+ @sections[section_id] << $2
1610
+ s = process_section(section_id, $3, nest-1)
1611
+ end
1612
+ else
1613
+ raise "can't happen"
1614
+ end
1615
+ else
1616
+ raise "can't happen"
1617
+ end
1618
+ end
1619
+
1620
+ def process_inline_amxml(s)
1621
+ puts "process_inline_amxml:%s" % s if @trace
1622
+ ret = ""
1623
+ s.to_s.scan(%r[(.*?)<<(.*?)>>]m) do |pre, amxml|
1624
+ ret << $1 << parse_inline_amxml(amxml)
1625
+ end
1626
+
1627
+ if Regexp.last_match
1628
+ ret + Regexp.last_match.post_match
1629
+ else
1630
+ s
1631
+ end
1632
+ end
1633
+
1634
+ def parse_inline_amxml(s)
1635
+ a = AmXml.parse(s, @opt)
1636
+ raise TemplateError, "illeagal inline amxml #{s}" unless a
1637
+ a.result(true)
1638
+ end
1639
+
1640
+
1641
+ def push_tag(indent, tag)
1642
+ @stack.unshift([indent, tag])
1643
+ end
1644
+
1645
+ def pop_tag(indent)
1646
+ ret = ""
1647
+ while @stack.size > 0 and @stack.first[0] >= indent
1648
+ tag = @stack.shift[1]
1649
+ if tag == :cdata
1650
+ ret << "]]>"
1651
+ else
1652
+ ret << "</#{tag}>"
1653
+ end
1654
+ end
1655
+ ret
1656
+ end
1657
+
1658
+ class AmXml
1659
+ attr_reader :tag, :attr, :src_name, :filters
1660
+ attr_reader :skipif, :skipunless, :for, :value
1661
+
1662
+ # Regexps borrowed from REXML rexml/baseparser.rb
1663
+ NCNAME_STR= '[\w:][\-\w\d.#]*?'
1664
+ NAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
1665
+
1666
+ R_TAG_ATTR = %r[\s*(#{NAME_STR})((?:\s*#{NAME_STR}\s*=\s*(["'])(?:.*?)\3)*)]um #"
1667
+ S_NO_SRC = %[\s*]
1668
+ S_SRC_AND_FILTERS = %q[(:([\\w\\d]*))\\s*(\\|.*?)*]
1669
+ S_FOR = %q[( \\[(.+)\\] )]
1670
+ S_COND = %q[\\?(\\!)?\\[(.+)\\]]
1671
+ S_VALUE = %q[\\{(.+)\\}]
1672
+ S_TRAILER = %q[((?: |\\-)*)]
1673
+ S_AMVARS = %[(#{S_FOR}|#{S_COND}|#{S_VALUE})?]
1674
+ R_AMXML_WITHOUT_TAG = %r[\A\s*(?:\/)?\s*(?:#{S_NO_SRC}|#{S_SRC_AND_FILTERS})*\s*#{S_AMVARS}\s*\Z]um
1675
+ R_AMXML = %r[\A#{R_TAG_ATTR}\s*(?:\/)?\s*(?:#{S_NO_SRC}|#{S_SRC_AND_FILTERS})*\s*#{S_AMVARS}\s*\Z]um
1676
+ R_AMXML_BLOCK_LINE = %r[^\s*<<\s*?(.*)\s*?<#{S_TRAILER}$]
1677
+ R_AMXML_BLOCK_START = %r[^\s*<<\s*((#{R_TAG_ATTR})?[^<>]*)\s*$]
1678
+ R_AMXML_ERB_LINE = %r[^\s*\%([ =])(.*)$]
1679
+
1680
+ def self.parse(s, opt={})
1681
+ self.new(opt).parse(s, opt[:trline])
1682
+ end
1683
+
1684
+ def initialize(opt)
1685
+ @opt = opt
1686
+ @tag = "_"
1687
+ @attr = ""
1688
+ @src_name = @filters = nil
1689
+ @skipif = @skipunless = @for = @value = nil
1690
+ end
1691
+
1692
+ def has_amxml_attrs?
1693
+ @src_name or @filters or @skipif or @skipunless or @for or @value
1694
+ end
1695
+
1696
+ def parse(s, trline=false)
1697
+ case s
1698
+ when R_AMXML_WITHOUT_TAG
1699
+ if trline
1700
+ @tag = "tr"
1701
+ else
1702
+ @tag = "_"
1703
+ end
1704
+ @src_name = $2.strip if $2
1705
+ @filters = $3[1..-1].strip if $3
1706
+ parse_am_vars($4)
1707
+ when %r[\A\s*([\w]+):([\-\w\d.]*)\s*(/?)\s*\Z] # <<aaa:xxx>>
1708
+ if $3 == "/"
1709
+ @tag = "#$1:#$2"
1710
+ else
1711
+ @tag, @src_name = $1, $2
1712
+ end
1713
+ @tag = "_" if @tag == nil or @tag == ""
1714
+ @attr = ""
1715
+ when R_AMXML
1716
+ @tag, @attr = $1.to_s, $2.to_s
1717
+ src_start = $4
1718
+ @src_name = $5
1719
+ @filters = $6[1..-1].strip if $6
1720
+ parse_am_vars($7)
1721
+ when "%"
1722
+ @tag = :cdata
1723
+ when /=\s*(\w+)\s*/
1724
+ @tag = [:put_stream,$1.intern]
1725
+ when /\s*(\w+)\s*=/
1726
+ @tag = [:change_stream,$1.intern]
1727
+ else
1728
+ return nil
1729
+ end
1730
+ parse_css_selecter_like
1731
+ self
1732
+ end
1733
+
1734
+ def parse_css_selecter_like
1735
+ while true
1736
+ case @tag
1737
+ when /^(\w+)([#\.])([\w-]+)(.*)$/
1738
+ @tag, cls = $1 + $4, $3
1739
+ case $2
1740
+ when "."
1741
+ @attr += " class=\"#{$3}\""
1742
+ when "#"
1743
+ @attr += " id=\"#{$3}\""
1744
+ else
1745
+ break
1746
+ end
1747
+ else
1748
+ break
1749
+ end
1750
+ end
1751
+ end
1752
+
1753
+ def parse_am_vars(s)
1754
+ case s
1755
+ when nil
1756
+ # do nothing
1757
+ when %r[#{S_FOR}]m
1758
+ @for = $2.strip
1759
+ when %r[#{S_COND}]m
1760
+ if $1 == "!"
1761
+ @skipif = $2.strip
1762
+ else
1763
+ @skipunless = $2.strip
1764
+ end
1765
+ when %r[#{S_VALUE}]m
1766
+ @value = $1.strip
1767
+ else
1768
+ raise "can't happen #{s}"
1769
+ end
1770
+ end
1771
+
1772
+ def result(single_tag)
1773
+ case @tag
1774
+ when :cdata
1775
+ return "<![CDATA["
1776
+ when Array
1777
+ case @tag[0]
1778
+ when :change_stream
1779
+ ret = "<_ am:filter='ChangeStream[#{@tag[1].inspect}]'"
1780
+ when :put_stream
1781
+ ret = "<_ am:filter='PutStream[#{@tag[1].inspect}]'"
1782
+ else
1783
+ raise "can't happen #{@tag.inspect}"
1784
+ end
1785
+ if single_tag
1786
+ ret << " />"
1787
+ else
1788
+ ret << " >"
1789
+ @tag = "_"
1790
+ end
1791
+ return ret
1792
+ end
1793
+ ret = "<"
1794
+ if @tag
1795
+ ret << @tag
1796
+ else
1797
+ ret << "_"
1798
+ end
1799
+ if @src_name and @src_name.size > 0
1800
+ am_src = @opt[:am_src] || "am:src"
1801
+ if @filters
1802
+ ret << make_attr(am_src, "#@src_name|#@filters")
1803
+ else
1804
+ ret << make_attr(am_src, @src_name)
1805
+ end
1806
+ else
1807
+ if @filters
1808
+ am_filter = @opt[:am_filter] || "am:filter"
1809
+ ret << make_attr(am_filter, @filters)
1810
+ end
1811
+ end
1812
+ if @for
1813
+ am_for = @opt[:am_for] || "am:for"
1814
+ ret << make_attr(am_for, @for)
1815
+ end
1816
+ if @skipif
1817
+ am_skipif = @opt[:am_skipif] || "am:skipif"
1818
+ ret << make_attr(am_skipif, @skipif)
1819
+ end
1820
+ if @skipunless
1821
+ am_skipif = @opt[:am_skipif] || "am:skipif"
1822
+ ret << make_attr(am_skipif, "not(#@skipunless)")
1823
+ end
1824
+ if @value
1825
+ am_v = @opt[:am_v] || "am:v"
1826
+ ret << make_attr(am_v, "HashDelegator.new($_) { {#@value} }")
1827
+ end
1828
+ ret << @attr.to_s
1829
+ if single_tag
1830
+ ret << " />"
1831
+ else
1832
+ ret << " >"
1833
+ end
1834
+ ret
1835
+ end
1836
+
1837
+ def make_attr(key, val)
1838
+ if val.include?(?')
1839
+ " #{key}=\"#{val}\""
1840
+ else
1841
+ " #{key}='#{val}'"
1842
+ end
1843
+ end
1844
+ end
6
1845
 
7
- # Map amrita:type to element_option
8
- DefaultTypeMap = {
9
- :use_args => { :use_args => true},
10
- :use_original_element=> { :use_original_element => true},
11
- :expand_by_member => { :expand_by_member => true },
12
- :erb => { :erb => true, :use_args=>true },
13
- :link => { :link_attr => [:href, [/javascript:.*/]] },
14
- :input => { :value_attr => :value },
15
- :checkbox => { :value_attr => :checked },
16
- :encoding_euc => { :encoding=>'EUC-JP' }
17
- }
1846
+ class LineProcessor
1847
+ attr_reader :cells
18
1848
 
19
- # keep id attribute of output if set.
20
- attr_accessor :keep_id
21
- # The name of attribute that will be used for template expandion by amrita.
22
- # You will need to set this if you use _id_ attribute fom DOM.
23
- # For expample, if this was set to "amrita_id",
24
- # you can use amrita_id for amrita and id for DOM.
25
- attr_accessor :amrita_id
26
-
27
- attr_accessor :encoding, :spec, :ie_hack, :debug_source, :eval_cdata_as_erb # :nodoc:
28
-
29
- attr_accessor :type_map, :parts_id # :nodoc:
30
-
31
- attr_reader :mod, :element_option, :root # :nodoc:
32
-
33
-
34
- def initialize(&block)
35
- @keep_id = false
36
- @amrita_id = nil
37
- @setuped = false
38
- @spec = nil
39
- @spec_proc = block
40
- @type_map = DefaultTypeMap.clone
41
- @ie_hack = false
42
- @eval_cdata_as_erb = false
43
- @element_option = Hash.new
44
- @parts_template = Hash.new
45
- @parts_id = nil
46
- @encoding = $KCODE
47
- @debug_source = nil
48
- end
49
-
50
- def setup
51
- return if @setuped
52
- setup_document
53
- @spec = get_spec
54
- @mod = @spec.create_template_module(@rexml_doc, @rexml_doc)
55
- @expander = get_expander
56
- @setuped = true
57
- end
58
-
59
- def setup_document
60
- return if @root
61
- @rexml_doc =
62
- if @eval_cdata_as_erb
63
- t = get_document_text
64
- t.gsub!(/<amrita:erb *id='(\w+)'>/, "<span id='\\1'><![CDATA[" )
65
- t.gsub!('</amrita:erb>', ']]></span>')
66
- REXML::Document.new(t)
67
- else
68
- get_document
69
- end
70
- @root = @rexml_doc.root
71
- end
72
-
73
- def get_root_option
74
- ret = {
75
- :delete_id => (not @keep_id),
76
- :key_attr_name=>( @amrita_id or :id),
77
- :ie_hack => @ie_hack,
78
- :eval_cdata_as_erb => @eval_cdata_as_erb,
79
- :debug_source => @debug_source,
80
- }
81
- case @encoding
82
- when "EUC"
83
- ret[:encoding]='EUC-JP'
84
- when "SJIS"
85
- ret[:encoding]='SHIFT_JIS'
86
- when "UTF8"
87
- ret[:encoding]='UTF-8'
88
- when "NONE"
89
- else
90
- raise "unkonw encoding #{@encoding}"
1849
+ def initialize(&block)
1850
+ @cells = []
1851
+ @cell_proc = block
1852
+ end
1853
+
1854
+ def parse_line(line)
1855
+ case line
1856
+ when %r[^\s*#]
1857
+ true
1858
+ when %r[^\s*\|\s*([\w:]*)\s*\|\|]
1859
+ parse_cells($1.strip, Regexp.last_match.post_match)
1860
+ true
1861
+ else
1862
+ false
1863
+ end
1864
+ end
1865
+
1866
+ def parse_cells(attr_key, cells)
1867
+ attr_key = :text if attr_key == ""
1868
+ cnt = 0
1869
+ splited = ""
1870
+ cells.chomp.scan(%r[([^\|]+?)((?!r\|)\|(\|?)|$)]) do |s, th_flag|
1871
+ if s[-1] == ?\\
1872
+ splited = s[0..-2] + "|"
1873
+ next
1874
+ else
1875
+ s = splited.to_s + s
1876
+ splited = ""
1877
+ end
1878
+
1879
+ @cells[cnt] ||= {}
1880
+ cell = @cells[cnt]
1881
+ if cell[attr_key]
1882
+ cell[attr_key] << "\n" << s
1883
+ else
1884
+ cell[attr_key] = s
1885
+ end
1886
+
1887
+ cell[:tag] = (th_flag == "||" ? :th : :td)
1888
+ cnt += 1
1889
+ end
1890
+ end
1891
+
1892
+ def get_result_xml
1893
+ @cells.collect do |c|
1894
+ tag, text = c[:tag], @cell_proc.call(c[:text])
1895
+ c.delete(:tag)
1896
+ c.delete(:text)
1897
+ if c.size == 0
1898
+ if text == nil or text.strip == ""
1899
+ ""
1900
+ else
1901
+ "<#{tag}>#{text}</#{tag}>"
1902
+ end
1903
+ else
1904
+ a = attr = c.collect do |k, v|
1905
+ next unless v
1906
+ v.strip!
1907
+ next if v == ""
1908
+ if v.include?(?')
1909
+ "#{k}=\"#{v}\""
1910
+ else
1911
+ "#{k}='#{v}'"
1912
+ end
1913
+ end.join(" ")
1914
+ "<#{tag} #{a}>#{text}</#{tag}>"
1915
+ end
1916
+ end.join("")
1917
+ end
91
1918
  end
92
- ret
93
1919
  end
94
1920
 
95
- def get_spec
96
- if @spec
97
- @spec
98
- elsif @spec_proc
99
- @spec = @spec_proc.call
100
- else
101
- @spec = make_default_spec(@root, @type_map, get_root_option, @element_option)
1921
+ class Hook
1922
+ attr_reader :stream, :cnt, :element_s
1923
+
1924
+ def initialize(&block)
1925
+ @hook_proc = block
1926
+ end
1927
+
1928
+ def call(stream, cnt, element_s,&block)
1929
+ @stream = stream
1930
+ @cnt = cnt
1931
+ @element_s = element_s
1932
+ @render_me_proc = block
1933
+ instance_eval(&@hook_proc)
1934
+ end
1935
+
1936
+ def render_me_with(it)
1937
+ stream << @render_me_proc.call(nil, it)
1938
+ end
1939
+
1940
+ def render_child(name, it)
1941
+ stream << @render_me_proc.call(name, it)
1942
+ end
1943
+
1944
+ class Renderer < Renderers::Base # :nodoc: all
1945
+ def initialize
1946
+ super("Hook")
1947
+ end
1948
+
1949
+ def generate_body(cg, de, element)
1950
+ cg.code("e = #{ cg.define_constant(element.to_s.inspect)}")
1951
+ cg.code("$_.call(__stream__, __cnt__, e) do |name, it| ")
1952
+ cg.level_up do
1953
+ cg.case_('name') do
1954
+ cg.when_("nil") do
1955
+ cg.code("render_with(it, __binding__)")
1956
+ end
1957
+ de.children.each do |c|
1958
+ next unless c.kind_of?(DynamicElement)
1959
+ cg.when_(c.name.intern.inspect) do
1960
+ cg.code("#{c.instance_name}.render_with(it, __binding__)")
1961
+ end
1962
+ end
1963
+ end
1964
+ end
1965
+ cg.code("end")
1966
+ end
102
1967
  end
103
- @spec = @spec.clone unless @spec.cloned
104
- @spec
105
1968
  end
1969
+ end
1970
+
1971
+ module Renderers # :nodoc: all
1972
+ class Base
1973
+ def initialize(type_name)
1974
+ @type_name = type_name
1975
+ end
1976
+
1977
+ def generate_when(cg)
1978
+ cg.when_(@type_name) do
1979
+ yield
1980
+ end
1981
+ end
1982
+ end
1983
+
1984
+ class ScalarRenderer < Renderers::Base
1985
+ def initialize
1986
+ super("ScalarData")
1987
+ end
106
1988
 
107
- def get_expander
108
- @spec.generate_expander(@mod)
1989
+ def generate_body(cg, de, element)
1990
+ de.element_render_code(cg, element) do
1991
+ cg.put_string_expression("$_.amrita_value")
1992
+ end
1993
+ end
109
1994
  end
110
1995
 
111
- def use_erb(b=TOPLEVEL_BINDING)
112
- @eval_cdata_as_erb = b
113
- @simple_cdata = true
1996
+ class NilRenderer < Base
1997
+ def initialize
1998
+ super("nil")
1999
+ end
2000
+
2001
+ def generate_body(cg, de, element)
2002
+ end
114
2003
  end
115
2004
 
116
- def define_parts(*parts_ids)
117
- setup_document
118
- parts_ids.each do |pid|
119
- case pid
120
- when Array
121
- pid.each { |id| define_parts(id) }
2005
+ class TrueRenderer < Base
2006
+ def initialize
2007
+ super("true")
2008
+ end
2009
+
2010
+ def generate_body(cg, de, element)
2011
+ cg.put_hpricot_node(element)
2012
+ end
2013
+ end
2014
+
2015
+ class HashRenderer < Base
2016
+ def initialize
2017
+ super("DictionaryData")
2018
+ end
2019
+
2020
+
2021
+ def generate_body(cg, de, element)
2022
+ de.element_render_code(cg, element) do
2023
+ de.children.each do |c|
2024
+ c.render_me(cg)
2025
+ end
2026
+ end
2027
+ end
2028
+ end
2029
+
2030
+ class ElseRenderer < Base
2031
+ def initialize
2032
+ super("Object")
2033
+ end
2034
+
2035
+ def generate_when(cg)
2036
+ cg.else_ do
2037
+ yield
2038
+ end
2039
+ end
2040
+
2041
+ def generate_body_new(cg, de, element)
2042
+ de.element_render_code(cg, element) do
2043
+ de.children.each do |c|
2044
+ c.render_me(cg)
2045
+ end
2046
+ end
2047
+ end
2048
+
2049
+ def generate_body(cg, de, element)
2050
+ if de.has_ruby?
2051
+ de.element_render_code(cg, element) do
2052
+ de.children.each do |c|
2053
+ c.render_me(cg)
2054
+ end
2055
+ end
2056
+ else
2057
+ cg.put_expression('raise %[type mismatch, got (#{$_.inspect}) for ' + element.to_s.inspect + ' ]')
2058
+ end
2059
+ end
2060
+ end
2061
+ end
2062
+
2063
+ module Filters
2064
+ include Renderers
2065
+
2066
+ class FilterArray < Array # :nodoc: all
2067
+ def initialize(*args)
2068
+ args.each { |a| self << a }
2069
+ end
2070
+
2071
+ def |(other)
2072
+ case other
2073
+ when Class
2074
+ a = self + [other.new]
2075
+ FilterArray.new(*a)
2076
+ when Filters::Base
2077
+ a = self + [other]
2078
+ FilterArray.new(*a)
122
2079
  when Symbol
123
- make_parts(@root, pid)
124
- raise "parts element #{pid} not found" unless @parts_template[pid]
125
- when Hash
126
- pid.each do |k, v|
127
- define_parts(k)
128
- @parts_template[k].define_parts(v) unless v == true
2080
+ self + FilterArray.new(FunctionFilter.new(other))
2081
+ when Array
2082
+ case other.first
2083
+ when Symbol
2084
+ self + FilterArray.new(FunctionFilter.new(*other))
2085
+ else
2086
+ self + other
129
2087
  end
130
2088
  else
131
- raise "#{pid.class} not supported"
2089
+ raise "not filter #{other.inspect}"
132
2090
  end
133
2091
  end
134
2092
  end
135
2093
 
136
- def make_parts(element, parts_id)
137
- element.each_element do |e|
138
- if e.attributes['id'] == parts_id.to_s
139
- e.parent.delete(e)
140
- pt = @parts_template[parts_id] = TemplateElement.new(e)
141
- pt.element_option[parts_id]= element_option[parts_id]
142
- pt.parts_id = parts_id
143
- pt.debug_source = debug_source
144
- pt.use_erb(@eval_cdata_as_erb) if @eval_cdata_as_erb
2094
+ class Base # :nodoc: all
2095
+ attr_accessor :next_
2096
+ def self.filter_method(*m)
2097
+ m.each do |name|
2098
+ module_eval <<-END
2099
+ def #{name}(*args,&block)
2100
+ next_.#{name}(*args,&block)
2101
+ end
2102
+ END
2103
+ end
2104
+ end
2105
+
2106
+ FilterMethods.each do |m|
2107
+ filter_method(m)
2108
+ end
2109
+
2110
+ def self.inherited(cls)
2111
+ super
2112
+ def cls.[](*args, &block)
2113
+ new(*args, &block)
2114
+ end
2115
+
2116
+ def cls.|(other)
2117
+ FilterArray.new(self.new) | other
2118
+ end
2119
+ end
2120
+
2121
+ def |(other)
2122
+ FilterArray.new(self) | other
2123
+ end
2124
+
2125
+ def parse_filter_a(f)
2126
+ case f = eval(f)
2127
+ when Class
2128
+ f.new
145
2129
  else
146
- make_parts(e, parts_id)
2130
+ f
147
2131
  end
148
2132
  end
149
2133
  end
150
2134
 
151
- def [](x)
152
- case x
153
- when Symbol
154
- @parts_template[x]
155
- else
156
- x = { parts_id=>x} if parts_id
157
- expand(out="", x)
158
- Amrita2::SanitizedString[out]
2135
+ class Attr < Base
2136
+ class Renderer < Renderers::Base
2137
+ include Util
2138
+ include OptionSupport
2139
+ def initialize(*args)
2140
+ parse_opt(*args)
2141
+ end
2142
+
2143
+ def generate_body(cg, de, element)
2144
+ use_body = (not de.has_dynamic?) && @opt.has_key?(:body)
2145
+ body_key = nil
2146
+ if use_body
2147
+ body_key = @opt[:body]
2148
+ body_key = :body unless body_key.kind_of?(Symbol)
2149
+ cg.code("body = $_.amrita_value(:#{body_key})")
2150
+ end
2151
+ if @opt.size > 0
2152
+ cg.code("a = {}")
2153
+ @opt.each do |key, v|
2154
+ next if key == :body
2155
+ if element.get_attribute(key)
2156
+ cg.case_("v = $_.amrita_value(:#{v})") do
2157
+ cg.when_("true") do
2158
+ cg.code("a[#{key.inspect}] = #{element.get_attribute(key).inspect}")
2159
+ end
2160
+ cg.when_("false, nil") do
2161
+ cg.code("a.delete(#{key.inspect})")
2162
+ end
2163
+ cg.else_ do
2164
+ cg.code("a[#{key.inspect}] = $_.amrita_value(:#{v})")
2165
+ end
2166
+ end
2167
+ else
2168
+ cg.code("v = $_.amrita_value(:#{v})")
2169
+ cg.code("a[#{key.inspect}] = v if v")
2170
+ end
2171
+ end
2172
+ else
2173
+ cg.code("a = $_ ")
2174
+ end
2175
+
2176
+ a = { }
2177
+ element.attributes.each do |k, v|
2178
+ next if @opt[k.intern]
2179
+ a[k] = v
2180
+ end
2181
+ cg.code("e = new_element(#{element.name.inspect}, #{a.inspect}.merge(a))")
2182
+ if use_body
2183
+ body_for_static(cg)
2184
+ else
2185
+ body_for_dynamic(cg, de, element)
2186
+ end
2187
+ end
2188
+
2189
+ def body_for_static(cg)
2190
+ cg.if_("body") do
2191
+ cg.put_string_expression("start_tag(e)")
2192
+ cg.put_string_expression("body.amrita_value")
2193
+ cg.put_string_expression("end_tag(e)")
2194
+ cg.else_ do
2195
+ cg.put_string_expression("e.to_s")
2196
+ end
2197
+ end
2198
+ end
2199
+
2200
+ def body_for_dynamic(cg, de, element)
2201
+ cg.put_string_expression("start_tag(e)")
2202
+ de.children.each do |c|
2203
+ c.render_me(cg)
2204
+ end
2205
+ cg.put_string_expression("end_tag(e)")
2206
+ end
2207
+ end
2208
+
2209
+ def initialize(*args)
2210
+ @args = args
2211
+ end
2212
+
2213
+ def setup_type_renderer(de)
2214
+ de.renderers.clear
2215
+ de.renderers << Renderer.new(*@args)
159
2216
  end
160
2217
  end
161
2218
 
162
- def expand(out, data=nil, &block)
163
- setup unless @setuped
164
- set_binding(@eval_cdata_as_erb) if @eval_cdata_as_erb.kind_of?(Binding)
165
- if data
166
- @expander.expand_template(out, data)
167
- else
168
- @mod.expand_template(out, &block)
2219
+
2220
+ class Default < Base
2221
+ def initialize(default_value_src)
2222
+ @default_value_src = default_value_src
2223
+ end
2224
+
2225
+ def value_filter_code(de, cg, value)
2226
+ cg.if_("$_") do
2227
+ super
2228
+ cg.else_ do
2229
+ cg.code("$_ = (#{@default_value_src.inspect})")
2230
+ end
2231
+ end
2232
+ end
2233
+
2234
+ def value_filter_code_old(de, cg, value)
2235
+ cg.code("$_ = $_ || (#{@default_value_src.inspect})")
2236
+ super
2237
+ end
2238
+ end
2239
+
2240
+ class Repeat < Base
2241
+ def initialize(cnt)
2242
+ @cnt = cnt.to_i
2243
+ end
2244
+
2245
+ def value_filter_code(de, cg, value)
2246
+ cg.code("$_ = $_ * #@cnt ")
2247
+ super
2248
+ end
2249
+ end
2250
+
2251
+ class Format < Base
2252
+ def initialize(format)
2253
+ @format = format
2254
+ end
2255
+ def value_filter_code(de, cg, value)
2256
+ cg.code("$_ = #{@format.inspect} % $_")
2257
+ super
169
2258
  end
170
2259
  end
171
2260
 
172
- def prettyprint=(f)
173
- warn 'prettyprint is not supported'
2261
+ class AcceptData < Base
2262
+ include Renderers
2263
+ def initialize(*types)
2264
+ @types = types
2265
+ end
2266
+
2267
+ def setup_type_renderer(de)
2268
+ nil_processed = true_processed = hook_processed = false
2269
+ @types.each do |t|
2270
+ case t
2271
+ when nil, NilClass
2272
+ de.renderers.unshift NilRenderer.new unless nil_processed
2273
+ nil_processed = true
2274
+ when true, TrueClass
2275
+ de.renderers << TrueRenderer.new unless true_processed
2276
+ true_processed = true
2277
+ when :hook
2278
+ de.renderers << Core::Hook::Renderer.new unless hook_processed
2279
+ hook_processed = true
2280
+ else
2281
+ raise ArgumentError,"unknown type for AcceptData #{t.inspect}"
2282
+ end
2283
+ end
2284
+ super
2285
+ end
174
2286
  end
175
2287
 
176
- def compact_space=(f)
177
- warn 'compact_space is not supported'
2288
+ class NoSanitize < Base
2289
+ def value_filter_code(de, cg, value)
2290
+ super
2291
+ cg.code("$_ = SanitizedString[$_]")
2292
+ end
178
2293
  end
179
2294
 
180
- def escaped_id=(f)
181
- warn 'escaped_id is not supported'
2295
+ class Each < Base
2296
+ include Util::OptionSupport
2297
+ def initialize(*args)
2298
+ parse_opt(*args)
2299
+ end
2300
+
2301
+ def value_filter_code(de, cg, value)
2302
+ var = "__cnt__#{de.class_name}"
2303
+ @opt.each do |k,va|
2304
+ cg.code("#{var} = __cnt__%#{va.size}")
2305
+ va.each_with_index do |v, n|
2306
+ case v
2307
+ when Symbol
2308
+ cg.code("$_[#{k.inspect}] = $_[#{v.inspect}] if #{var} == #{n}")
2309
+ when String
2310
+ cg.code("$_[#{k.inspect}] = #{v.inspect} if #{var} == #{n}")
2311
+ else
2312
+ raise("unknown Each type #{v.inspect}")
2313
+ end
2314
+ end
2315
+ end
2316
+ super
2317
+ end
182
2318
  end
183
2319
 
184
- def xml=(f)
185
- warn 'xml option is not supported'
2320
+ class ToHash < Base
2321
+ include Util::OptionSupport
2322
+ def initialize(key)
2323
+ @key = key
2324
+ end
2325
+
2326
+ def value_filter_code(de, cg, value)
2327
+ case @key
2328
+ when Symbol
2329
+ cg.code("$_ = { #{@key.inspect} => $_ }")
2330
+ when Hash
2331
+ cg.code("$_ = { ")
2332
+ @key.each do |k,v|
2333
+ cg.code(" #{k.inspect} => $_.amrita_value(#{v.inspect}),")
2334
+ end
2335
+ cg.code("}")
2336
+ end
2337
+
2338
+ super
2339
+ end
186
2340
  end
187
2341
 
188
- def asxml=(f)
189
- warn 'asxml option is not supported'
2342
+ class InlineRuby < Base # :nodoc: all
2343
+ include Runtime
2344
+ include Renderers
2345
+ include Util
2346
+
2347
+ def setup_type_renderer(de)
2348
+ de.renderers << HashRenderer.new unless de.renderers.find {|r| r.kind_of?(HashRenderer) }
2349
+ super
2350
+ end
2351
+
2352
+ def generate_dynamic_element(de, e)
2353
+ a = e.attributes
2354
+ ret = super
2355
+ return ret if ret
2356
+
2357
+ # check if element has any of inlineruby attributes
2358
+ if %w(am_skipif am_for am_v).any? { |k| a[de.__send__(k)] }
2359
+ filters_src = e.attributes[de.am_filter]
2360
+ filters = de.root.compile_filters(e, nil, filters_src)
2361
+ Core::DynamicElement.new(de, nil , e, filters)
2362
+ else
2363
+ nil
2364
+ end
2365
+ end
2366
+
2367
+ def loop_check_code(de, cg)
2368
+ foreach_ = de.am_for_value
2369
+ if foreach_
2370
+ cg.if_("__cnt__ < 0") do
2371
+ #cg.code("$_ = eval(#{foreach_.inspect}, __binding__)")
2372
+ cg.code("$_ = ")
2373
+ cg.eval(foreach_.inspect)
2374
+ super
2375
+ end
2376
+ else
2377
+ super
2378
+ end
2379
+ end
2380
+
2381
+ def value_filter_code(de, cg, value)
2382
+ cond = de.am_skipif_value
2383
+ value = de.am_v_value
2384
+
2385
+ if cond
2386
+ cg.code("amrita_set_context_value($_)")
2387
+ #cg.code("return '' if eval(#{with_context_value_expression(cond).inspect},__binding__)")
2388
+ cg.code("return '' if \\")
2389
+ cg.eval(with_context_value_expression(cond).inspect)
2390
+ end
2391
+
2392
+ if value
2393
+ cg.code("amrita_set_context_value($_)")
2394
+ #cg.code("$_ = eval(#{with_context_value_expression(value).inspect},__binding__)")
2395
+ cg.code("$_ = ")
2396
+ cg.eval(with_context_value_expression(value).inspect)
2397
+ end
2398
+ super
2399
+ end
2400
+
2401
+ private
2402
+ def with_context_value_expression(src)
2403
+ [
2404
+ "$_ = amrita_get_context_value",
2405
+ "ret = (#{src})",
2406
+ "amrita_set_context_value($_)",
2407
+ "ret"
2408
+ ].join(";")
2409
+ end
190
2410
  end
191
2411
 
192
- def pre_format=(f)
193
- warn 'pre_format option is not supported'
2412
+ module NVarMixin
2413
+ private
2414
+
2415
+ def make_tupple_code(cg)
2416
+ init_code = @names.collect do |n|
2417
+ "$_.amrita_value(:#{n})"
2418
+ end.join(",")
2419
+ cg.code("$_ = Tuple[#{init_code}]")
2420
+ end
2421
+
2422
+ def replace_args(element)
2423
+ element.attributes.each do|k,v|
2424
+ element.set_attribute(k, replace_attr_args(v))
2425
+ end
2426
+ children = element.children.collect do |c|
2427
+ case c
2428
+ when Hpricot::Elem
2429
+ replace_args(c)
2430
+ when Hpricot::Text
2431
+ Hpricot::Text.new(replace_text_args(c.to_s))
2432
+ else
2433
+ raise "not implemented #{c.class}"
2434
+ end
2435
+ end
2436
+ element.children.clear
2437
+ children.each do |c|
2438
+ element.children << c
2439
+ end
2440
+ element
2441
+ end
2442
+
2443
+ def replace_attr_args(s)
2444
+ s.gsub(/(.)?\$(\d)/) do |ss|
2445
+ if $1 == "$"
2446
+ "$#$2"
2447
+ else
2448
+ $1.to_s + '#{Util::sanitize_attribute_value($_[' + ($2.to_i-1).to_s + '].to_s)}'
2449
+ end
2450
+ end
2451
+ end
2452
+
2453
+ def replace_text_args(s)
2454
+ s.gsub(/(.)?\$(\d)/) do |ss|
2455
+ if $1 == "$"
2456
+ "$#$2"
2457
+ else
2458
+ $1.to_s + '#{$_[' + ($2.to_i-1).to_s + '].amrita_value}'
2459
+ end
2460
+ end
2461
+ end
194
2462
  end
195
2463
 
196
- def use_compiler=(f)
197
- warn 'use_compiler option is not supported'
2464
+ class NVar < Filters::Base
2465
+ include NVarMixin
2466
+ def initialize(*names)
2467
+ @names = names
2468
+ end
2469
+
2470
+ def renderer_code(de, cg, element)
2471
+ make_tupple_code(cg) if @names.size > 0
2472
+ s = replace_args(element).to_s
2473
+ cg.put_string_expression(s.inspect.gsub(/\\#/, "#"))
2474
+ end
198
2475
  end
199
2476
 
200
- def get_document
201
- nil
2477
+ class Eval < NVar
2478
+ def initialize(*names)
2479
+ @names = names
2480
+ end
2481
+
2482
+ private
2483
+
2484
+ def make_tupple_code(cg)
2485
+ cg.code("amrita_set_context_value($_)")
2486
+ init_code = @names.collect do |n|
2487
+ case n
2488
+ when Symbol
2489
+ "$_.amrita_value(:#{n})"
2490
+ else
2491
+ "eval(#{with_context_value_expression(n).inspect},__binding__)"
2492
+ end
2493
+ end.join(",")
2494
+ cg.code("$_ = Tuple[#{init_code}]")
2495
+ end
2496
+
2497
+ def with_context_value_expression(src)
2498
+ [
2499
+ "$_ = amrita_get_context_value",
2500
+ "ret = (#{src})",
2501
+ "amrita_set_context_value($_)",
2502
+ "ret"
2503
+ ].join(";")
2504
+ end
202
2505
  end
203
2506
 
204
- def make_default_spec(root, type_map, option, element_option)
205
- s = Core::RootSpec.new(option.clone)
206
- make_default_spec1(root, option, [s], type_map, element_option)
207
- s
208
- end
209
-
210
- def make_default_spec1(e, option, spec_stack, type_map, element_option)
211
- key_attr = option[:key_attr_name].to_s
212
- id_ = e.attributes[key_attr]
213
- if id_
214
- sym = id_.intern
215
- opt = get_option(e, option, type_map, element_option, sym)
216
- s = Core::DynamicElementSpec.new(sym, nil, opt)
217
- s.parent = spec_stack.first
218
- spec_stack.first.children << s
219
- spec_stack.unshift(s)
220
- e.elements.each do |c|
221
- make_default_spec1(c, option, spec_stack, type_map, element_option)
222
- end
223
- spec_stack.shift
224
- spec_stack.first.check_double_id if option[:check_double_id]
225
- else
226
- e.elements.each do |c|
227
- make_default_spec1(c, option, spec_stack, type_map, element_option)
228
- end
229
- end
230
- end
231
-
232
- def get_option(e, option, type_map, element_option, sym)
233
- ret = option.clone
234
- a = e.attributes
235
- typ = (a['amrita:type'] || element_option[sym])
236
- case typ
237
- when Symbol, String
238
- default_opt = type_map[typ.intern] || { typ.intern=>true }
239
- ret.merge!(default_opt) if default_opt
240
- a.delete('amrita:type')
241
- when Hash
242
- default_opt = typ
243
- ret.merge!(default_opt) if default_opt
244
- a.delete('amrita:type')
245
- end
246
- if opt = a['amrita:option']
247
- ret.merge!(eval("{#{opt} }"))
248
- a.delete('amrita:option')
249
- end
250
- ret
251
- end
252
- end
2507
+ class NVarForAttr < Filters::Base
2508
+ def initialize(*names)
2509
+ @names = names
2510
+ end
253
2511
 
254
- class TemplateFile < Template
255
- include Amrita2
256
- def initialize(fname, &block)
257
- super(&block)
258
- @fname = fname
2512
+ def renderer_code(de, cg, element)
2513
+ make_tupple_code(cg) if @names.size > 0
2514
+ super
2515
+ end
2516
+
2517
+ def element_render_code(de, cg, element, &block)
2518
+ if (element.name == 'span' && element.attributes.size == 0)
2519
+ yield
2520
+ else
2521
+ replace_args(element)
2522
+ if element.children.size > 0
2523
+ cg.put_string_expression(element.stag.output('').inspect.gsub(/\\#/, "#"))
2524
+ yield
2525
+ cg.put_constant(element.etag.output(''))
2526
+ else
2527
+ cg.put_string_expression(%["#{element.to_s}"])
2528
+ end
2529
+ end
2530
+ end
2531
+
2532
+ private
2533
+ def make_tupple_code(cg)
2534
+ init_code = @names.collect do |n|
2535
+ "$_.amrita_value(:#{n})"
2536
+ end.join(",")
2537
+ cg.code("a = [#{init_code}]")
2538
+ end
2539
+
2540
+ def replace_args(element)
2541
+ element.attributes.each do|k,v|
2542
+ element.set_attribute(k, replace_attr_args(v))
2543
+ end
2544
+ end
2545
+ def replace_attr_args(s)
2546
+ s.gsub(/\$(\d)/) do |ss|
2547
+ '#{Util::sanitize_attribute_value(a[' + ($1.to_i-1).to_s + '].to_s)}'
2548
+ end
2549
+ end
259
2550
  end
260
2551
 
261
- def get_document
262
- REXML::Document.new(File.new(@fname))
2552
+ class FunctionFilter < Filters::Base
2553
+ def initialize(sym, *args)
2554
+ @sym, @args = sym, args
2555
+ end
2556
+
2557
+ def value_filter_code(de, cg, element)
2558
+ cg.code("$_ = $_.send(#{@sym.inspect}, *#{@args.inspect})")
2559
+ super
2560
+ end
263
2561
  end
264
2562
 
265
- def get_document_text
266
- File.new(@fname).read
2563
+ class CommandFilter < Filters::Base
2564
+ def initialize(*args)
2565
+ @args = args.collect do |a|
2566
+ a.inspect
2567
+ end.join(' ')
2568
+ end
2569
+
2570
+ def define_element_method(de, cg, &block)
2571
+ super do
2572
+ block.call
2573
+ cg.code "pipe = IO.popen(#@args, 'r+')"
2574
+ cg.code "pipe.write __stream__; pipe.close_write"
2575
+ cg.code "__stream__ = pipe.read ; pipe.close "
2576
+ end
2577
+ end
267
2578
  end
268
- end
269
2579
 
270
- class TemplateText < Template
271
- include Amrita2
272
- def initialize(text, &block)
273
- super(&block)
274
- @text = text
2580
+ class Trace < Filters::Base
2581
+ SEP = "\n" + "-" * 80 + "\n"
2582
+ def filter_element(de, element)
2583
+ if de.tracer
2584
+ old_s = element.to_s
2585
+ new_element = super
2586
+ new_s = new_element.to_s
2587
+ if old_s != new_s
2588
+ de.tracer << SEP << "before filter:\n" <<
2589
+ old_s << SEP << "after filter :\n" << new_s << SEP
2590
+ end
2591
+ new_element
2592
+ else
2593
+ super
2594
+ end
2595
+ end
2596
+
2597
+ def value_filter_code(de, cg, value)
2598
+ s = de.element.stag.output("")
2599
+ cg.code(%[Tracer << "%s:%s:%s" % [#{s.inspect},"in", $_.inspect] ])
2600
+ super
2601
+ end
2602
+
2603
+ def method_body_code(de, cg)
2604
+ super
2605
+ s = de.element.stag.output("")
2606
+ cg.code(%[Tracer << "%s:%s:%s" % [#{s.inspect},"out", __stream__] ])
2607
+ end
2608
+ end
2609
+
2610
+ class Join < Base
2611
+ def initialize(sep)
2612
+ @sep = case sep
2613
+ when :br ; "<br />"
2614
+ when :nbsp ; "&#160;"
2615
+ else ; sep
2616
+ end
2617
+ end
2618
+
2619
+ def filter_element(de, e)
2620
+ a = e.children.dup
2621
+ a.each do |n|
2622
+ case n
2623
+ when Hpricot::CData, Hpricot::Elem
2624
+ e.insert_after(Hpricot::Text.new(@sep), n) unless n == a.last
2625
+ when Hpricot::Text
2626
+ e.insert_after(Hpricot::Text.new(@sep), n) unless n == a.last
2627
+ n.content.strip!
2628
+ n.content.gsub!(/\s*\n\s*/, @sep)
2629
+ end
2630
+ end
2631
+ e
2632
+ end
2633
+ end
2634
+
2635
+ class ChangeStream < Base
2636
+ def initialize(name)
2637
+ @name = name
2638
+ end
2639
+
2640
+ def method_body_code(de, cg)
2641
+ cg.code("#ChangeStream")
2642
+ cg.with_stream(@name) do
2643
+ super
2644
+ end
2645
+ end
275
2646
  end
276
2647
 
277
- def get_document
278
- REXML::Document.new(@text)
2648
+ class PutStream < Base
2649
+ def initialize(name)
2650
+ @name = name
2651
+ end
2652
+
2653
+ def method_body_code(de, cg)
2654
+ cg.code("#PutStream")
2655
+ cg.put_stream(@name)
2656
+ end
279
2657
  end
280
2658
 
281
- def get_document_text
282
- @text
2659
+ class ModuleExtendFilter< Base
2660
+ def initialize(mod)
2661
+ @mod = mod
2662
+ end
2663
+
2664
+ def value_filter_code(de, cg, element)
2665
+ cg.code("$_.extend(#@mod)")
2666
+ super
2667
+ end
283
2668
  end
284
2669
  end
285
2670
 
286
- class TemplateElement < Template
287
- def initialize(e, &block)
288
- super(&block)
289
- @element = e
2671
+ class HashDelegator
2672
+ include DictionaryData
2673
+ def initialize(parent, &block)
2674
+ @parent = parent
2675
+ @added_data = block.call
2676
+ raise 'block did not return a Hash' unless @added_data.kind_of?(DictionaryData)
290
2677
  end
291
2678
 
292
- def get_document
293
- doc = REXML::Document.new
294
- doc << @element
295
- doc
2679
+ def [](key)
2680
+ self.send(key)
2681
+ rescue NoMethodError
2682
+ nil
2683
+ end
2684
+
2685
+ def id
2686
+ @parent.id
296
2687
  end
297
2688
 
298
- def get_document_text
299
- doc = REXML::Document.new
300
- doc << @element
301
- doc.to_s
2689
+ def method_missing(sym, *args, &block)
2690
+ if @added_data.has_key?(sym)
2691
+ @added_data[sym]
2692
+ else
2693
+ if @parent.kind_of?(DictionaryData)
2694
+ @parent.amrita_value(sym)
2695
+ else
2696
+ @parent.send(sym)
2697
+ end
2698
+ end
302
2699
  end
303
2700
  end
2701
+
2702
+ SanitizedString = Util::SanitizedString
2703
+ Template = Core::Template
304
2704
  end