bijou 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/ChangeLog.txt +4 -0
  2. data/LICENSE.txt +58 -0
  3. data/README.txt +48 -0
  4. data/Rakefile +105 -0
  5. data/doc/INSTALL.rdoc +260 -0
  6. data/doc/README.rdoc +314 -0
  7. data/doc/releases/bijou-0.1.0.rdoc +60 -0
  8. data/examples/birthday/birthday.rb +34 -0
  9. data/examples/holiday/holiday.rb +61 -0
  10. data/examples/holiday/letterhead.txt +4 -0
  11. data/examples/holiday/signature.txt +9 -0
  12. data/examples/phishing/letter.txt +29 -0
  13. data/examples/phishing/letterhead.txt +4 -0
  14. data/examples/phishing/phishing.rb +21 -0
  15. data/examples/phishing/signature.txt +9 -0
  16. data/examples/profile/profile.rb +46 -0
  17. data/lib/bijou.rb +15 -0
  18. data/lib/bijou/backend.rb +542 -0
  19. data/lib/bijou/cgi/adapter.rb +201 -0
  20. data/lib/bijou/cgi/handler.rb +5 -0
  21. data/lib/bijou/cgi/request.rb +37 -0
  22. data/lib/bijou/common.rb +12 -0
  23. data/lib/bijou/component.rb +108 -0
  24. data/lib/bijou/config.rb +60 -0
  25. data/lib/bijou/console/adapter.rb +167 -0
  26. data/lib/bijou/console/handler.rb +4 -0
  27. data/lib/bijou/console/request.rb +26 -0
  28. data/lib/bijou/context.rb +431 -0
  29. data/lib/bijou/diagnostics.rb +87 -0
  30. data/lib/bijou/errorformatter.rb +322 -0
  31. data/lib/bijou/exception.rb +39 -0
  32. data/lib/bijou/filters.rb +107 -0
  33. data/lib/bijou/httprequest.rb +108 -0
  34. data/lib/bijou/httpresponse.rb +268 -0
  35. data/lib/bijou/lexer.rb +513 -0
  36. data/lib/bijou/minicgi.rb +159 -0
  37. data/lib/bijou/parser.rb +1026 -0
  38. data/lib/bijou/processor.rb +404 -0
  39. data/lib/bijou/prstringio.rb +400 -0
  40. data/lib/bijou/webrick/adapter.rb +174 -0
  41. data/lib/bijou/webrick/handler.rb +32 -0
  42. data/lib/bijou/webrick/request.rb +45 -0
  43. data/script/cgi.rb +25 -0
  44. data/script/console.rb +7 -0
  45. data/script/server.rb +7 -0
  46. data/test/t1.cfg +5 -0
  47. data/test/tc_config.rb +26 -0
  48. data/test/tc_filter.rb +25 -0
  49. data/test/tc_lexer.rb +120 -0
  50. data/test/tc_response.rb +103 -0
  51. data/test/tc_ruby.rb +62 -0
  52. data/test/tc_stack.rb +50 -0
  53. metadata +121 -0
@@ -0,0 +1,4 @@
1
+
2
+ Date: <%= Time.now.strftime("%x") %>
3
+
4
+ <& content &>
@@ -0,0 +1,9 @@
1
+ <%args>
2
+ signed
3
+ </%args>
4
+ <%
5
+ options = ['Sincerely', 'Regards', 'Kind regards', 'Best regards', 'Warmly']
6
+ closing = options[rand(options.length)]
7
+ %>
8
+ <%= closing %>,
9
+ <%= signed %>
@@ -0,0 +1,29 @@
1
+ <%! container='letterhead.txt' %>
2
+ <%args>
3
+ name => 'to whom it may concern'
4
+ </%args>
5
+
6
+ Subject: With the complements of Mrs. former 1st lady
7
+
8
+ Dear <%= name %>,
9
+
10
+ Good day and compliments. I am writing you regarding an urgent matter. May I
11
+ crave your indulgence to open this discussion by a formal letter of this sort.
12
+ I am making this contact on behalf of my sister, wife of the late general and
13
+ former head of state of our republic.
14
+
15
+ Recently, due to unfortunate circumstances, we have had to consider the
16
+ option of moving her assets of One Hundred and Fifty-Three Millions United
17
+ States Dollars out of the country. We are seeking a business partner with
18
+ banking coordinates capable of accommodating such huge amounts. We have put
19
+ in place an instrument of payment for USD $<%= 30 + rand(15) %>M, which is
20
+ now in a dedicated account. If you agree to a business proposition, this amount
21
+ will be transferred also into your account as remuneration for your assistance
22
+ with our heartfelt thanks. I implore your consideration of this grave matter.
23
+
24
+ I await your prompt response.
25
+
26
+ <& signature.txt, signed => 'Solicitor R.U. Naeve' &>
27
+
28
+ DISCLAIMER: This mock phishing letter is for demonstration purposes only.
29
+ ---- cut here ----
@@ -0,0 +1,4 @@
1
+
2
+ Date: <%= Time.now.strftime("%x") %>
3
+
4
+ <& content &>
@@ -0,0 +1,21 @@
1
+
2
+ $:.push '../../lib'
3
+
4
+ require 'bijou'
5
+
6
+ processor = Bijou::Processor.new
7
+
8
+ context = processor.load('letter.txt');
9
+
10
+ people = [ 'David', 'Richard', 'Tiffany' ]
11
+
12
+ people.each { |who|
13
+ args = { 'name' => who }
14
+
15
+ context.render(args)
16
+
17
+ print context.output;
18
+
19
+ # We reuse the context, so clear the ouput buffers.
20
+ context.clear
21
+ }
@@ -0,0 +1,9 @@
1
+ <%args>
2
+ signed
3
+ </%args>
4
+ <%
5
+ options = ['Sincerely', 'Regards', 'Kind regards', 'Best regards', 'Warmly']
6
+ closing = options[rand(options.length)]
7
+ %>
8
+ <%= closing %>,
9
+ <%= signed %>
@@ -0,0 +1,46 @@
1
+
2
+ $:.push '../..'
3
+
4
+ require 'rubygems'
5
+ require 'ruby-prof'
6
+ require 'bijou/processor'
7
+
8
+ RubyProf.start
9
+
10
+ parser = Bijou::Parser.new
11
+
12
+ class_name = "BirthdayView"
13
+
14
+ how_many = 1000
15
+
16
+ how_many.times {
17
+ class_text = parser.parse(class_name, <<EOS)
18
+ <%args>
19
+ name => 'to whom it may concern'
20
+ </%args>
21
+
22
+ Dear <%= name %>,
23
+ I\'m writing this letter today to send my best wishes for a happy birthday.
24
+
25
+ Sincerely,
26
+ Your boss
27
+
28
+ ---- cut here ----
29
+ EOS
30
+
31
+ people = [ 'David' ]
32
+
33
+ people.each { |who|
34
+ args = { 'name' => who }
35
+
36
+ context = Bijou::Context.new(Bijou::Config.new)
37
+
38
+ letter = Bijou::Processor.execute(context, class_text, class_name, args)
39
+
40
+ # print letter
41
+ }
42
+ }
43
+
44
+ result = RubyProf.stop
45
+ printer = RubyProf::GraphHtmlPrinter.new(result)
46
+ printer.print(STDOUT, 0)
data/lib/bijou.rb ADDED
@@ -0,0 +1,15 @@
1
+ #--
2
+ #
3
+ # Bijou - A web page templating framework for Ruby.
4
+ #
5
+ # Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
6
+ #
7
+ # Author: Todd Lucas <tl@dogandponyshow.org>
8
+ #
9
+ #++
10
+ #
11
+ # :include: doc/README.rdoc
12
+ # :title: Bijou - Web templates for Ruby
13
+ #
14
+ require 'bijou/component'
15
+ require 'bijou/processor'
@@ -0,0 +1,542 @@
1
+ #
2
+ # Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
3
+ #
4
+ # backend.rb - Parser backend code generator
5
+ #
6
+ require 'bijou/common'
7
+ require 'bijou/filters'
8
+
9
+ module Bijou
10
+ module Parse
11
+
12
+ #
13
+ # A parser backend component for rendering the %args collection.
14
+ #
15
+ class ArgumentCollection
16
+ def initialize()
17
+ super()
18
+ @args = {}
19
+ end
20
+
21
+ attr_accessor :args
22
+
23
+ def add_argument(name, expression, filename, line)
24
+ # TODO: Args should preserver order; change to array.
25
+ @args[name] = [ expression, filename, line ]
26
+ end
27
+
28
+ def render_args(method, use_markers)
29
+ result = ''
30
+
31
+ @args.each { |key, value|
32
+ expression = value[0]
33
+ filename = value[1]
34
+ line = value[2]
35
+
36
+ # REVIEW: This is a bit verbose.
37
+ if expression
38
+ result << "#line #{line} #{filename}\n" if use_markers
39
+ result << " #{key} = args.has_key?('#{key}') ? args['#{key}']" +
40
+ " : #{expression}\n"
41
+ else
42
+ result << "#line #{line} #{filename}\n" if use_markers
43
+ result << " #{key} = args.has_key?('#{key}') ? args['#{key}']" +
44
+ " : @context.argument_exception('#{method}', '#{key}')\n"
45
+ end
46
+ }
47
+
48
+ return result
49
+ end
50
+ end
51
+
52
+ #
53
+ # The base class for the backend code generator. Used to render a component
54
+ # or a piece of a component.
55
+ #
56
+ class Target
57
+ def initialize
58
+ @output = ''
59
+ @args = nil
60
+ end
61
+
62
+ attr_accessor :args
63
+
64
+ # Escape text for embedding in a single-quoted string.
65
+ def escape_single(text)
66
+ # We use the block because \' has special meaning in substitution strings.
67
+ return text.gsub(/[\'\\]/) { |ch| "\\" + ch }
68
+ end
69
+ private :escape_single
70
+
71
+ def render_part(text) # print 'text'
72
+ if text.length > 0
73
+ text = escape_single(text)
74
+ @output << " print '#{text}'\n"
75
+ end
76
+ end
77
+
78
+ def render_line(text) # puts 'text'
79
+ if text.length > 0
80
+ text = escape_single(text)
81
+ @output << " puts '#{text}'\n"
82
+ end
83
+ end
84
+
85
+ def render_expr(expr) # print expr
86
+ if expr.length > 0
87
+ @output << " print #{expr}\n"
88
+ end
89
+ end
90
+
91
+ def render_code_(code) # code
92
+ @output << "#{code}"
93
+ end
94
+
95
+ def render_code(code) # code
96
+ @output << "#{code}\n"
97
+ end
98
+
99
+ #
100
+ # Render a Perl-oriented line marker comment , for use in generating stack
101
+ # traces at runtime. Any line information returned with an Exception object
102
+ # will correspond to the source code that was generated from the component.
103
+ # This line marker comment will allow the runtime to find the corresponding
104
+ # line in the component's markup text.
105
+ #
106
+ def render_marker(line, filename)
107
+ render_code("#line #{line} #{filename}")
108
+ end
109
+
110
+ def render_args(method, use_markers)
111
+ result = ''
112
+ if @args
113
+ result << @args.render_args(method, use_markers)
114
+ end
115
+ return result
116
+ end
117
+ end
118
+
119
+ class Def < Target
120
+ def initialize(name)
121
+ super()
122
+ @name = name
123
+ end
124
+
125
+ attr_reader :name
126
+
127
+ def renderMethod
128
+ result = ''
129
+
130
+ result << " def #{@name}\n"
131
+ result << @output
132
+ result << " end\n"
133
+
134
+ return result
135
+ end
136
+ end
137
+
138
+ #
139
+ # The class for the backend code generator that is used to render the
140
+ # %method block. It may contain its own %args block.
141
+ #
142
+ class Method < Def
143
+ def initialize(name)
144
+ super(name)
145
+ end
146
+
147
+ def render_method(use_markers)
148
+ result = ''
149
+
150
+ if @name == 'fini'
151
+ result << " def #{@name}()\n"
152
+ else
153
+ result << " def #{@name}(args)\n"
154
+ end
155
+
156
+ if @name == 'init'
157
+ # For init, call super before derived code.
158
+ result << " super\n"
159
+ end
160
+
161
+ result << render_args(@name, use_markers)
162
+ result << @output
163
+
164
+ if @name == 'fini'
165
+ # For fini, call super after derived code.
166
+ result << " super\n"
167
+ end
168
+ result << " end\n"
169
+
170
+ return result
171
+ end
172
+
173
+ end
174
+
175
+ #
176
+ # The class for the backend code generator that is used to render an entire
177
+ # component. It may contain a top-level %args block. This %args block is
178
+ # replicated for the %init block.
179
+ #
180
+ class Component < Target
181
+ def initialize(use_markers)
182
+ @name = ''
183
+ @componentBase = 'Bijou::Component'
184
+ @componentName = ''
185
+ @output = ''
186
+ @defs = []
187
+ @directives = {}
188
+ @source_filename = nil
189
+ @cache_filename = nil
190
+ @use_markers = use_markers
191
+ end
192
+
193
+ attr_accessor :directives, :source_filename, :cache_filename
194
+
195
+ def add_method(d)
196
+ @defs.push(d)
197
+ end
198
+
199
+ def component=(componentName)
200
+ @componentName = componentName
201
+ end
202
+
203
+ def component_base=(componentBase)
204
+ @componentBase = componentBase
205
+ end
206
+
207
+ def require_list=(list)
208
+ @requireList = list
209
+ end
210
+
211
+ def to_s
212
+ result = ''
213
+
214
+ if @directives.has_key?('base')
215
+ @componentBase = @directives['base']
216
+ end
217
+
218
+ if @args
219
+ @defs.each {|d|
220
+ if d.name == 'init'
221
+ # NOTE: The init block has the same argument expansion as render.
222
+ d.args = @args.clone
223
+ break
224
+ end
225
+ }
226
+ end
227
+
228
+ result << "require 'bijou/component'\n"
229
+ if @requireList
230
+ @requireList.each {|path|
231
+ result << "require '#{path}'\n"
232
+ }
233
+ end
234
+ result << "class #{@componentName} < #{@componentBase}\n"
235
+ if @directives.has_key?('container')
236
+ result << " def self.container\n"
237
+ result << " '#{@directives['container']}'\n"
238
+ result << " end\n"
239
+ end
240
+
241
+ # REVIEW: Do we need to escape? The filenames appear to always use
242
+ # forward slashes.
243
+ if @source_filename
244
+ result << " def self.source_filename\n"
245
+ result << " '#{@source_filename}'\n"
246
+ result << " end\n"
247
+ end
248
+
249
+ if @cache_filename
250
+ result << " def self.cache_filename\n"
251
+ result << " '#{@cache_filename}'\n"
252
+ result << " end\n"
253
+ end
254
+
255
+ result << " def render(args)\n"
256
+ result << render_args('render', @use_markers)
257
+ result << @output;
258
+ result << " end\n"
259
+
260
+ @defs.each {|d|
261
+ result << d.render_method(@use_markers)
262
+ }
263
+
264
+ result << "end\n"
265
+
266
+ return result
267
+ end
268
+ end
269
+
270
+ #
271
+ # The backend is used by the parser to generate a Ruby representation of a
272
+ # Bijou component.
273
+ #
274
+ class Backend
275
+ class ParseContext
276
+ Normal = 1
277
+ MethodTag = 2
278
+ end
279
+
280
+ def initialize(diagnostics, trace, use_markers)
281
+ @trace = trace
282
+ @use_markers = use_markers
283
+
284
+ @buffer = ''
285
+ @parseContext = ParseContext::Normal
286
+
287
+ @diagnostics = diagnostics
288
+
289
+ @currentComponent = @component = Bijou::Parse::Component.new(use_markers)
290
+ @argsCollection = nil
291
+ @isDefContext = false
292
+
293
+ # TODO: Make these installable.
294
+ @filters = {
295
+ 'u' => Bijou::EncodeURL.new,
296
+ 'h' => Bijou::EncodeHTML.new,
297
+ 'a' => Bijou::EncodeAttributeValue.new,
298
+ 't' => Bijou::EncodeTrim.new,
299
+ 'w' => Bijou::EncodeWiki.new,
300
+ }
301
+ end
302
+
303
+ # Used to accumulate raw text from the parser.
304
+ def buffer(s)
305
+ @buffer << s
306
+ end
307
+
308
+ # Used to flush the buffer to the output stream with a newline
309
+ def puts_buffer()
310
+ if @isDefContext
311
+ @currentComponent.render_code(@buffer)
312
+ else
313
+ @currentComponent.render_line(@buffer)
314
+ end
315
+
316
+ @buffer = ''
317
+ end
318
+
319
+ # Flushes the buffer to the output stream without a newline. Often used
320
+ # before an inline tag <% ... %>.
321
+ def print_buffer()
322
+ if @isDefContext
323
+ @currentComponent.render_code_(@buffer)
324
+ else
325
+ @currentComponent.render_part(@buffer)
326
+ end
327
+
328
+ @buffer = ''
329
+ end
330
+
331
+ def message(s, line=nil, column=nil)
332
+ m = Bijou::Parse::Message.new
333
+ m.at(line, column)
334
+ m << s
335
+ @diagnostics.add_message(m)
336
+ end
337
+
338
+ def warning(s, line=nil, column=nil)
339
+ m = Bijou::Parse::Warning.new
340
+ m.at(line, column)
341
+ m << s
342
+ @diagnostics.add_warning(m)
343
+ end
344
+
345
+ def error(s, line=nil, column=nil)
346
+ m = Bijou::Parse::Error.new
347
+ m.at(line, column)
348
+ m << s
349
+ @diagnostics.add_error(m)
350
+ end
351
+
352
+ def trace(s)
353
+ puts s if @trace
354
+ end
355
+
356
+ #--
357
+ #
358
+ # Parser methods
359
+ #
360
+ #++
361
+
362
+ def render(component, source_filename=nil, cache_filename=nil,
363
+ component_base=nil, require_list=nil)
364
+ @component.component = component
365
+ @component.source_filename = source_filename
366
+ @component.cache_filename = cache_filename
367
+ if component_base
368
+ @component.component_base = component_base
369
+ end
370
+ if require_list
371
+ @component.require_list = require_list
372
+ end
373
+ @component.to_s
374
+ end
375
+
376
+ def markup_section(buffer, filename, line)
377
+ @currentComponent.render_marker(line, filename) if @use_markers
378
+
379
+ if @isDefContext
380
+ @currentComponent.render_code(buffer)
381
+ else
382
+ @currentComponent.render_part(buffer)
383
+ end
384
+ end
385
+
386
+ def tag_open(tagStart)
387
+ print_buffer
388
+
389
+ trace "\nopentag: #{tagStart}"
390
+ end
391
+
392
+ def named_start_tag(prefix, name, line, column)
393
+ case prefix
394
+ when 'method'
395
+ if @parseContext != ParseContext::Normal
396
+ error("method declared at nested scope", line, column)
397
+ return
398
+ end
399
+
400
+ @parseContext = ParseContext::MethodTag
401
+ @currentComponent = Bijou::Parse::Method.new(name)
402
+ @isDefContext = false
403
+
404
+ when 'init'
405
+ if @parseContext != ParseContext::Normal
406
+ error("init declared at nested scope", line, column)
407
+ return
408
+ end
409
+
410
+ @parseContext = ParseContext::MethodTag
411
+ @currentComponent = Bijou::Parse::Method.new(prefix)
412
+ @isDefContext = true
413
+
414
+ when 'fini'
415
+ if @parseContext != ParseContext::Normal
416
+ error("fini declared at nested scope", line, column)
417
+ return
418
+ end
419
+
420
+ @parseContext = ParseContext::MethodTag
421
+ @currentComponent = Bijou::Parse::Method.new(prefix)
422
+ @isDefContext = true
423
+
424
+ when 'args'
425
+ if @argsCollection
426
+ error("args may not be declared within another args section.", line, column)
427
+ return
428
+ end
429
+
430
+ # NOTE: We don't change parse context.
431
+ @argsCollection = Bijou::Parse::ArgumentCollection.new
432
+
433
+ if @currentComponent.args
434
+ warning("args section overrides previous args section.", line, column)
435
+ end
436
+
437
+ @currentComponent.args = @argsCollection
438
+ else
439
+ raise "unexpected named tag #{prefix}"
440
+ end
441
+ end
442
+
443
+ def named_end_tag(prefix)
444
+ if @argsCollection
445
+ @argsCollection = nil;
446
+ return
447
+ end
448
+
449
+ case @parseContext
450
+ when ParseContext::MethodTag;
451
+ @component.add_method(@currentComponent)
452
+ @currentComponent = @component
453
+
454
+ @parseContext = ParseContext::Normal
455
+ @isDefContext = false
456
+ when 'args'
457
+ if !@argsCollection
458
+ error("unexpected args end tag", line, column)
459
+ end
460
+
461
+ @argsCollection = nil
462
+ else
463
+ end
464
+ end
465
+
466
+ def inline_tag(text, filename, line)
467
+ @currentComponent.render_marker(line, filename) if @use_markers
468
+ @currentComponent.render_code(text)
469
+ end
470
+
471
+ def output_tag(text, filters, filename, line)
472
+ filtered = "(#{text}).to_s"
473
+
474
+ if filters
475
+ filters.each{|filter|
476
+ if @filters.has_key? filter
477
+ filtered = @filters[filter].render(filtered)
478
+ else
479
+ warning("unrecognized output tag filter '#{filter}'", filename, line)
480
+ end
481
+ }
482
+ end
483
+
484
+ @currentComponent.render_marker(line, filename) if @use_markers
485
+ @currentComponent.render_expr(filtered)
486
+ end
487
+
488
+ def call_tag(identifier, args, indirect, filename, line)
489
+ # Convert the argument list [[arg1, val1], [arg2, val2], ...] into
490
+ # a list of formatted string values.
491
+ list = []
492
+
493
+ args.each {|item|
494
+ list.push "'#{item[0]}' => #{item[1]}"
495
+ }
496
+
497
+ # Join the list of strings into a formatted hash.
498
+ text = "{#{list.join(', ')}}"
499
+
500
+ @currentComponent.render_marker(line, filename) if @use_markers
501
+
502
+ if indirect
503
+ # REVIEW: The indirection method currently shares the arguments with the
504
+ # method to be invoked. This may not be desirable. We would need an
505
+ # different call syntax to do otherwise. If different arguments are
506
+ # required, invoke may be called directly in a code block.
507
+ @currentComponent.render_code(" @context.invoke(#{identifier}(#{text}), #{text})")
508
+ else
509
+ @currentComponent.render_code(" @context.invoke('#{identifier}', #{text})")
510
+ end
511
+ end
512
+
513
+ def directive_tag(hash, line, column)
514
+ trace "\nDirectives:"
515
+ hash.each { |key, value|
516
+ trace "#{key} => #{value}"
517
+
518
+ if value =~ /^[\'\"](.*)[\'\"]$/
519
+ value = $1
520
+ end
521
+
522
+ if @component.directives.has_key?(key)
523
+ warning("directive '#{key}' overrides previous definition", line, column)
524
+ end
525
+ @component.directives[key] = value
526
+ }
527
+ end
528
+
529
+ def add_argument(argName, argValue, filename, line, column)
530
+ if @argsCollection
531
+ if @argsCollection.args.has_key?(argName)
532
+ error("argument '#{argName}' overrides previous delcaration", line, column)
533
+ end
534
+ @argsCollection.add_argument(argName, argValue, filename, line)
535
+ else
536
+ error("unexpected argument", line, column)
537
+ end
538
+ end
539
+ end
540
+
541
+ end # module Parse
542
+ end # module Bijou