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,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