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
+ require 'bijou/console/adapter'
3
+
4
+ Bijou::Console::Adapter.handle
@@ -0,0 +1,26 @@
1
+
2
+ require 'bijou/minicgi'
3
+ require 'bijou/httprequest'
4
+
5
+ module Bijou
6
+ module Console
7
+ #
8
+ # A very simple request object that takes a query string from the command
9
+ # line and feeds it to the component.
10
+ #
11
+ class Request < Bijou::HttpRequest
12
+ def initialize(qs)
13
+ super()
14
+
15
+ query_string = ::CGI::parse(qs) || {}
16
+
17
+ @query_string = Bijou::MiniCGI.singularize(query_string)
18
+
19
+ @form = @query_string.clone
20
+ @params = @query_string.clone
21
+
22
+ @http_method = 'GET'
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,431 @@
1
+ #
2
+ # Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
3
+ #
4
+ # context.rb - Contains Context and related classes.
5
+ #
6
+ require 'bijou/common'
7
+ require 'bijou/config'
8
+
9
+ module Bijou
10
+ # Represents a single component in a component/container chain.
11
+ class Frame
12
+ attr_reader :output
13
+ attr_reader :args
14
+ attr_reader :component
15
+
16
+ def initialize(component)
17
+ @component = component
18
+ @output = ''
19
+ @args = {}
20
+ end
21
+
22
+ def flush
23
+ s = @output
24
+ @output = ''
25
+ s
26
+ end
27
+ end
28
+
29
+ # Each comonent/container chain is represented by a Stack object. The stack
30
+ # class contains a list of Frame objects and an output buffer.
31
+ class Stack
32
+ def initialize()
33
+ @frame_index = 0
34
+ @frames = []
35
+ @buffer = ''
36
+ end
37
+
38
+ attr_reader :buffer
39
+ attr_reader :frames
40
+
41
+ def index
42
+ @frame_index
43
+ end
44
+
45
+ def to_s
46
+ s = ''
47
+ @frames.each {|f|
48
+ s << "'#{f.component}': "
49
+ s << "'#{f.output}' "
50
+ }
51
+ s << " -> '#{@buffer}'"
52
+ s
53
+ end
54
+
55
+ def push_frame(component)
56
+ if @frames.length > 0
57
+ @buffer << @frames[-1].flush
58
+ end
59
+
60
+ @frames.push(Frame.new(component))
61
+ end
62
+
63
+ def start
64
+ @frame_index = @frames.length - 1
65
+ end
66
+
67
+ def top_frame
68
+ @frames[@frame_index]
69
+ end
70
+
71
+ def next_frame
72
+ @frame_index -= 1
73
+ top_frame
74
+ end
75
+
76
+ def peek_frame(offset = 1)
77
+ @frames[@frame_index - offset]
78
+ end
79
+
80
+ def flush_frame()
81
+ @buffer << top_frame.flush
82
+ end
83
+
84
+ def pop_frame()
85
+ flush_frame()
86
+ # @buffer << self.output
87
+ @frames.pop()
88
+ end
89
+
90
+ def output
91
+ @frames[@frame_index].output
92
+ end
93
+
94
+ def flush
95
+ s = @buffer
96
+ @buffer = ''
97
+ s
98
+ end
99
+ end
100
+
101
+ # Represents execution state shared by the execution contexts.
102
+ class Environment
103
+ def initialize()
104
+ @component = nil
105
+ @trace_level = Log::None
106
+ @trace_buffer = true
107
+ @log = ''
108
+ @trace = ''
109
+ end
110
+
111
+ attr_accessor :component, :trace_level, :trace_buffer
112
+
113
+ # Logs with carriage return.
114
+ def log(level, str)
115
+ @log << str + "\n"
116
+ end
117
+
118
+ # Logs without carriage return.
119
+ def log_(level, str)
120
+ @log << str
121
+ end
122
+
123
+ def get_log
124
+ @log
125
+ end
126
+
127
+ # Trace with carriage return.
128
+ def trace(level, str)
129
+ if level <= @trace_level
130
+ if @trace_buffer
131
+ @trace << str + "\n"
132
+ else
133
+ puts str
134
+ end
135
+ end
136
+ end
137
+
138
+ # Trace without carriage return.
139
+ def trace_(level, str)
140
+ if level <= @trace_level
141
+ if @trace_buffer
142
+ @trace << str
143
+ else
144
+ print str
145
+ end
146
+ end
147
+ end
148
+
149
+ def get_trace
150
+ @trace
151
+ end
152
+ end
153
+
154
+ #
155
+ # When a Bijou file is loaded by the processor, each component that is
156
+ # encountered causes a Bijou::Component class to be instantiated in
157
+ # the context. This context can then be executed with a set of input
158
+ # arguments, causing each component to be evaluated and the output to be
159
+ # rendered. A context may be used to invoke the same arrangement of
160
+ # components any number of times with different arguments.
161
+ #
162
+ class Context
163
+ attr_accessor :component_callback
164
+ attr_accessor :container_callback
165
+ attr_reader :stack
166
+ attr_reader :config
167
+ attr_reader :environment
168
+
169
+ attr_accessor :cgi
170
+ attr_accessor :request
171
+ attr_accessor :response
172
+
173
+ def initialize(config, environment=nil)
174
+ if !environment
175
+ environment = Environment.new
176
+ environment.trace_level = config.trace_level
177
+ environment.trace_buffer = config.trace_buffer
178
+ end
179
+
180
+ @args = {}
181
+ @stack = Stack.new
182
+ @config = config
183
+ @environment = environment
184
+ @container_callback = nil
185
+ @component_callback = nil
186
+
187
+ @request = nil
188
+ @response = nil
189
+
190
+ @cgi = nil
191
+ end
192
+
193
+ # Called after clone
194
+ def reset()
195
+ @args = {}
196
+ @stack = Stack.new
197
+ end
198
+
199
+ def write(str)
200
+ @stack.output << str
201
+ end
202
+
203
+ def writeline(str)
204
+ @stack.output << str + "\n"
205
+ end
206
+
207
+ def output
208
+ @stack.buffer
209
+ end
210
+
211
+ def clear
212
+ @stack.flush
213
+ end
214
+
215
+ # Used to retrieve the last active frame in the event of an exception.
216
+ def source_filename
217
+ if @environment.component
218
+ @environment.component.class.source_filename
219
+ else
220
+ nil
221
+ end
222
+ end
223
+
224
+ # Used to retrieve the last active frame in the event of an exception.
225
+ def cache_filename
226
+ if @environment.component
227
+ @environment.component.class.cache_filename
228
+ else
229
+ nil
230
+ end
231
+ end
232
+
233
+ def add_component(component)
234
+ @stack.push_frame(component)
235
+
236
+ # If the owner registered a container load handler, invoke after the
237
+ # component has been added.
238
+ if component.class.container && @container_callback
239
+ @container_callback.call(self, component.class.container)
240
+ end
241
+ end
242
+
243
+ def render(args)
244
+ trace Bijou::Log::Info, "render init"
245
+
246
+ prev_component = @environment.component
247
+
248
+ # Init may render, after the headers but before the page text.
249
+ @stack.frames.each { |frame|
250
+ @environment.component = frame.component
251
+ frame.component.init(args)
252
+ }
253
+
254
+ @environment.component = prev_component
255
+
256
+ # Render the component chain to the response target.
257
+ render_component(args)
258
+
259
+ trace Bijou::Log::Info, "render fini"
260
+
261
+ # Fini may still render to the stream, at the end after the page text.
262
+ @stack.frames.each { |frame|
263
+ @environment.component = frame.component
264
+ frame.component.fini()
265
+ }
266
+
267
+ @environment.component = prev_component
268
+
269
+ return @output
270
+ end
271
+
272
+ # The render method renders the component chain.
273
+ def render_component(args)
274
+ if @stack.frames.length == 0
275
+ return
276
+ end
277
+
278
+ # Set top-level args so they are available to all components.
279
+ @args = args
280
+
281
+ # Start with the outer-most container at the end of the list.
282
+ @stack.start
283
+
284
+ prev_component = @environment.component
285
+
286
+ component = @stack.top_frame.component
287
+ @environment.component = component
288
+ component.render(@args)
289
+ @environment.component = prev_component
290
+
291
+ # This flushes the component's output to the stack's buffer.
292
+ @stack.flush_frame
293
+ end
294
+
295
+ # Container modules render their callers by calling the content method
296
+ # once at the location where the caller's output should be rendered.
297
+ def call_next(extra={})
298
+ if @stack.index == 0
299
+ raise 'next called too many times'
300
+ end
301
+
302
+ # Flush the first part of the component's output to the stack's buffer.
303
+ @stack.flush_frame
304
+
305
+ prev_component = @environment.component
306
+
307
+ # REVIEW: Argument merge and override is experimental.
308
+ # Render the child content.
309
+ component = @stack.next_frame.component
310
+ @environment.component = component
311
+ component.render(@args.merge(extra))
312
+ @environment.component = prev_component
313
+
314
+ # The remainder of the content will be flushed at the end of render.
315
+ # @stack.flush_frame
316
+ end
317
+
318
+ def try_fetch_next
319
+ if @stack.index == 0
320
+ nil
321
+ else
322
+ @stack.peek_frame
323
+ end
324
+ end
325
+
326
+ def fetch_next
327
+ try_fetch_next || raise('fetch_next cannot find next component')
328
+ end
329
+
330
+ def try_fetch_remainder
331
+ if @stack.index == 0
332
+ nil
333
+ else
334
+ @stack.frames[0, @stack.index].reverse
335
+ end
336
+ end
337
+
338
+ def fetch_remainder
339
+ try_fetch_remainder || raise('fetch_remainder cannot find next component')
340
+ end
341
+
342
+ # Used to invoke a method or a component. The output is rendered to a
343
+ # string, which is returned as the result. If no match is found returns nil.
344
+ def sinvoke(name, args)
345
+ # Does this look like a method? We don't include setters, etc. (?!=).
346
+ if name =~ /[_a-z][_a-z0-9]*/
347
+ prev_component = @environment.component
348
+
349
+ # Do any of the components support this method?
350
+ @stack.frames.each { |f|
351
+ if f.component.respond_to?(name)
352
+ @stack.flush_frame
353
+ @environment.component = f.component
354
+ m = f.component.method(name)
355
+ m.call(args)
356
+ @environment.component = prev_component
357
+ return @stack.top_frame.flush
358
+ end
359
+ }
360
+
361
+ @environment.component = prev_component
362
+ end
363
+
364
+ # Delegate to context owner
365
+ if @component_callback
366
+ # The subcontext wraps a new stack for the component, but shares
367
+ # common data. This allows us to encapsulate the rendered output
368
+ # into a separate buffer chain.
369
+ subcontext = self.clone
370
+ subcontext.reset
371
+
372
+ # TODO: Handle new context and buffer merging.
373
+ @component_callback.call(subcontext, name, args)
374
+
375
+ return subcontext.stack.flush
376
+ end
377
+
378
+ nil
379
+ end
380
+
381
+ # Used to invoke a method or a component. The output is rendered to the
382
+ # buffer. Returns true if a match was found.
383
+ def invoke(name, args)
384
+ if name == 'content'
385
+ # REVIEW: This is an experimental feature. An alternative (without
386
+ # argument support) is <%content>, but it is an unclosed tag.
387
+ return call_next(args)
388
+ end
389
+
390
+ if text = sinvoke(name, args)
391
+ write text
392
+ return true
393
+ end
394
+
395
+ false
396
+ end
397
+
398
+ def argument_exception(method, name)
399
+ # BUGBUG: We need to print the page (component) name.
400
+ raise "Expected argument '#{name}' to method '#{method}'"
401
+ end
402
+
403
+ # Logs with carriage return.
404
+ def log(level, str)
405
+ @environment.log(level, str)
406
+ end
407
+
408
+ # Logs without carriage return.
409
+ def log_(level, str)
410
+ @environment.log_(level, str)
411
+ end
412
+
413
+ def get_log()
414
+ @environment.get_log()
415
+ end
416
+
417
+ # Trace with carriage return.
418
+ def trace(level, str)
419
+ @environment.trace(level, str)
420
+ end
421
+
422
+ # Trace without carriage return.
423
+ def trace_(level, str)
424
+ @environment.trace_(level, str)
425
+ end
426
+
427
+ def get_trace()
428
+ @environment.get_trace()
429
+ end
430
+ end
431
+ end