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,87 @@
1
+ #
2
+ # Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
3
+ #
4
+ # diagnostics.rb - Classes related to error handling
5
+ #
6
+ require 'bijou/common'
7
+
8
+ module Bijou::Parse
9
+ end
10
+
11
+ class Bijou::Parse::Message
12
+ def initialize
13
+ @line = nil
14
+ @column = nil
15
+ @prefix = nil
16
+ @text = ''
17
+ end
18
+
19
+ attr_reader :line, :column
20
+
21
+ def at(line, column)
22
+ @line = line
23
+ @column = column
24
+ end
25
+
26
+ def <<(s)
27
+ @text << s.to_s
28
+ end
29
+
30
+ def text
31
+ result = ''
32
+ result << @prefix if @prefix
33
+ result << "(#{@line}, #{@column || 0})" if @line
34
+ result << ": " + @text
35
+ result
36
+ end
37
+ end
38
+
39
+ class Bijou::Parse::Warning < Bijou::Parse::Message
40
+ def initialize
41
+ super()
42
+ @prefix = 'warning'
43
+ end
44
+ end
45
+
46
+ class Bijou::Parse::Error < Bijou::Parse::Message
47
+ def initialize
48
+ super()
49
+ @prefix = 'error'
50
+ end
51
+ end
52
+
53
+ class Bijou::Parse::Diagnostics
54
+ def initialize
55
+ @log = []
56
+ @messages = []
57
+ @messages = []
58
+ @warnings = []
59
+ @errors = []
60
+ end
61
+
62
+ attr_reader :errors, :warnings, :messages
63
+
64
+ def add_message(msg)
65
+ @messages.push(msg)
66
+ @log.push(msg)
67
+ end
68
+
69
+ def add_warning(msg)
70
+ @warnings.push(msg)
71
+ @log.push(msg)
72
+ end
73
+
74
+ def add_error(msg)
75
+ @errors.push(msg)
76
+ @log.push(msg)
77
+ end
78
+
79
+ def text
80
+ result = ''
81
+ @log.each { |s|
82
+ result << s.text
83
+ result << "\n"
84
+ }
85
+ result
86
+ end
87
+ end
@@ -0,0 +1,322 @@
1
+ #
2
+ # Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
3
+ #
4
+ # errorformatter.rb - Classes used to corelate Ruby errors with Bijou files
5
+ #
6
+ require 'cgi'
7
+ require 'bijou/common'
8
+ require 'bijou/exception'
9
+
10
+ module Bijou
11
+ #
12
+ # The error formatter is used at runtime to locate the origin of errors
13
+ # with respect to the source Bijou file. During processing, the text in a
14
+ # Bijou file is turned into Ruby code and cached as a separate file.
15
+ # It is the class contained in this file that is the runtime representation
16
+ # of a Bijou component. When a runtime error occurs, the exception refers
17
+ # to a location in the cached file. The ErrorFormatter class is used to
18
+ # find the location in the original Bijou file by referring to #line comments
19
+ # embedded in the cached file and back tracking from there.
20
+ #
21
+ class ErrorFormatter
22
+ attr_accessor :title, :error, :errors, :warnings, :stack
23
+
24
+ def initialize
25
+ @title = ''
26
+ @error = nil
27
+ @errors = []
28
+ @warnings = []
29
+ @stack = []
30
+ @adjacent_lines = 0
31
+
32
+ # Context information
33
+ @filename = ''
34
+ @context_lines = []
35
+ @error_line = 0
36
+ @error_column = 0
37
+ @context = nil
38
+ end
39
+
40
+ def set_context(filename, lines, context, error_line, error_column)
41
+ @filename = filename
42
+ @context_lines = lines
43
+ @context = context
44
+ @error_line = error_line
45
+ @error_column = error_column
46
+ end
47
+
48
+ def format
49
+ raise StandardException, "Invalid error formatter"
50
+ end
51
+
52
+ def self.format_error(format, adjacent_lines, context=nil)
53
+ if format == :html
54
+ formatter = ErrorFormatterHTML.new
55
+ else
56
+ formatter = ErrorFormatterText.new
57
+ end
58
+
59
+ @context = context
60
+
61
+ result = ''
62
+
63
+ error_line = 0
64
+ error_column = 0
65
+
66
+ error = $!
67
+
68
+ if $!.kind_of?(Bijou::ParseError)
69
+ formatter.title = $!.to_s
70
+
71
+ filename = error.filename
72
+
73
+ formatter.errors.replace(error.diagnostics.errors)
74
+ formatter.warnings.replace(error.diagnostics.warnings)
75
+
76
+ if error.diagnostics.errors
77
+ # We use the first error as the origin.
78
+ selected_error = error.diagnostics.errors[0]
79
+ error_line = selected_error.line
80
+ error_column = selected_error.column
81
+ end
82
+ else
83
+ if $!.kind_of?(Bijou::EvalError)
84
+ formatter.error = error.cause
85
+ filename = error.filename
86
+ cachename = error.cachename
87
+ else
88
+ formatter.error = $!
89
+
90
+ # REVIEW: Are there any cases where the context would override
91
+ # the EvalError location?
92
+ if context
93
+ filename = context.source_filename
94
+ cachename = context.cache_filename
95
+ elsif error.respond_to?('filename')
96
+ filename = error.filename
97
+ cachename = error.cachename
98
+ end
99
+ end
100
+
101
+ # First, look for '(eval):#' in the error itself.
102
+ eval_line = self.find_eval_line(formatter.error)
103
+ if eval_line == 0
104
+ # If not found, walk the stack.
105
+ $@.each {|err|
106
+ eval_line = self.find_eval_line(err)
107
+ if eval_line > 0
108
+ break
109
+ end
110
+ }
111
+ end
112
+
113
+ # Find the location in the original source using the line numbers
114
+ # in the cache.
115
+ if eval_line > 0 && cachename
116
+ error_line = self.get_source_file_line(cachename, eval_line)
117
+ end
118
+ end
119
+
120
+ if filename && File.exists?(filename) && error_line > 0
121
+ lines = self.get_file_context(filename, error_line, adjacent_lines)
122
+
123
+ formatter.set_context(filename, lines, adjacent_lines,
124
+ error_line, error_column)
125
+ else
126
+ # TODO: Provide a default message using the best available info.
127
+ end
128
+
129
+ formatter.stack.replace($@)
130
+
131
+ result = formatter.format
132
+
133
+ return result
134
+ end
135
+
136
+ def self.get_file_context(filename, line, adjacent_lines)
137
+ file = File.new(filename, "r")
138
+
139
+ min = line > adjacent_lines ? line - adjacent_lines : 1
140
+ counter = 1;
141
+ list = []
142
+
143
+ while line = file.gets
144
+ line.chomp!
145
+
146
+ if counter >= min
147
+ list.push({ 'number' => counter, 'text' => line })
148
+ if counter >= min + 2 * adjacent_lines
149
+ break
150
+ end
151
+ end
152
+
153
+ counter += 1
154
+ end
155
+
156
+ file.close
157
+
158
+ return list
159
+ end
160
+
161
+ def self.get_source_file_line(cachename, eval_line)
162
+ file = File.new(cachename, "r")
163
+ counter = 1
164
+ most_recent_line = 0
165
+ while line = file.gets
166
+ line.chomp!
167
+
168
+ if line =~ /\#line\s+(\d+)/
169
+ most_recent_line = $1.to_i
170
+ # Start back by one to account for the comment line.
171
+ most_recent_line -= 1
172
+ else
173
+ most_recent_line += 1
174
+ end
175
+
176
+ if counter >= eval_line
177
+ return most_recent_line
178
+ end
179
+
180
+ counter += 1
181
+ end
182
+ file.close
183
+ return 0
184
+ end
185
+
186
+ def self.find_eval_line(error)
187
+ line = 0
188
+
189
+ #
190
+ # This section requires a heuristic to find the most likely line that
191
+ # is the origin of the error.
192
+ #
193
+ if error.to_s =~ /\(eval\)\:(\d+)\: parse error/
194
+ return $1.to_i
195
+ elsif error.to_s =~ /\(eval\)\:(\d+)\:/
196
+ # Any eval line
197
+ return $1.to_i
198
+ end
199
+ return 0
200
+ end
201
+ end
202
+
203
+ class ErrorFormatterText < ErrorFormatter
204
+ def format
205
+ result = ''
206
+
207
+ # Lead with the error message.
208
+ result << $!.to_s
209
+
210
+ #
211
+ # The error description.
212
+ #
213
+ result << "\n\n"
214
+ if @error # This should be $! if set.
215
+ result << error
216
+ end
217
+
218
+ # Print a list of errors and warnings.
219
+ errors.each {|m|
220
+ result << m.text + "\n"
221
+ }
222
+ warnings.each {|m|
223
+ result << m.text + "\n"
224
+ }
225
+
226
+ if @error_line && @context_lines.length > 0
227
+ # Show where it occurred
228
+ result << "\nContext\n"
229
+
230
+ digits = Integer(Math.log10(@error_line + @adjacent_lines) + 1)
231
+
232
+ @context_lines.each {|line|
233
+ result << sprintf("%*d: %s\n", digits, line['number'], line['text'])
234
+ if @error_line == line['number']
235
+ if @error_column > 0
236
+ result << sprintf("%*s\n", digits + 1 + @error_column, '^')
237
+ end
238
+ end
239
+ }
240
+ end
241
+
242
+ result << "\nStack:\n"
243
+ result << @stack.join("\n")
244
+
245
+ return result
246
+ end
247
+ end
248
+
249
+ class ErrorFormatterHTML < ErrorFormatter
250
+ def format
251
+ result = ''
252
+
253
+ # Lead with the error message.
254
+ result << "<h3>Message</h3>\n"
255
+ result << "<pre>"
256
+ result << ::CGI::escapeHTML($!.to_s)
257
+ result << "</pre>\n"
258
+
259
+ #
260
+ # The error description.
261
+ #
262
+ # result << "\n\n"
263
+ # if @error # This should be $! if set.
264
+ # result << "<pre>"
265
+ # result << error
266
+ # result << "</pre>\n"
267
+ # end
268
+
269
+ errors.each {|m|
270
+ result << "<div>"
271
+ result << ::CGI::escapeHTML(m.text)
272
+ result << "</div>\n"
273
+ }
274
+ warnings.each {|m|
275
+ result << "<div>"
276
+ result << ::CGI::escapeHTML(m.text)
277
+ result << "</div>\n"
278
+ }
279
+
280
+ if @error_line && @context_lines.length > 0
281
+ # Show where it occurred
282
+ result << "<h3>Context</h3>\n"
283
+ result << "<pre>\n"
284
+
285
+ digits = Integer(Math.log10(@error_line + @adjacent_lines) + 1)
286
+
287
+ @context_lines.each {|line|
288
+ if @error_line == line['number']
289
+ result << '<strong>'
290
+ end
291
+ result << sprintf("%*d: %s\n", digits, line['number'],
292
+ ::CGI::escapeHTML(line['text']))
293
+ if @error_line == line['number']
294
+ result << "</strong>"
295
+ if @error_column > 0
296
+ result << sprintf("%*s\n", digits + 1 + @error_column, '^')
297
+ end
298
+ end
299
+ }
300
+
301
+ result << "</pre>\n"
302
+ end
303
+
304
+ result << "<h3>Stack</h3>\n"
305
+ stack.each {|m|
306
+ result << "<div>"
307
+ result << ::CGI::escapeHTML(m.to_s)
308
+ result << "</div>\n"
309
+ }
310
+
311
+ return result
312
+ end
313
+
314
+ def self.style
315
+ return "
316
+ body { font-family: Arial,sans-serif; }
317
+ pre { font-family: Courier New,monospace; }
318
+ pre strong { color: red; }
319
+ "
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Copyright (c) 2007-2008 Todd Lucas. All rights reserved.
3
+ #
4
+ # exception.rb - Definitions of Bijou-specific exception classes
5
+ #
6
+ require 'bijou/common'
7
+
8
+ module Bijou
9
+ class FileNotFound < IOError
10
+ def initialize(filename)
11
+ @filename = filename
12
+ end
13
+ end
14
+
15
+ class EndRequest < Exception
16
+ end
17
+
18
+ class ParseError < Exception
19
+
20
+ attr_reader :filename, :diagnostics
21
+
22
+ def initialize(filename, diagnostics)
23
+ @filename = filename
24
+ @diagnostics = diagnostics
25
+ end
26
+ end
27
+
28
+ class EvalError < Exception
29
+
30
+ attr_reader :filename, :cachename, :cause
31
+
32
+ # The cause is used to capture the text of the originating exeception.
33
+ def initialize(filename, cachename, cause)
34
+ @filename = filename
35
+ @cachename = cachename
36
+ @cause = cause
37
+ end
38
+ end
39
+ end