bijou 0.1.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 (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