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.
- data/ChangeLog.txt +4 -0
- data/LICENSE.txt +58 -0
- data/README.txt +48 -0
- data/Rakefile +105 -0
- data/doc/INSTALL.rdoc +260 -0
- data/doc/README.rdoc +314 -0
- data/doc/releases/bijou-0.1.0.rdoc +60 -0
- data/examples/birthday/birthday.rb +34 -0
- data/examples/holiday/holiday.rb +61 -0
- data/examples/holiday/letterhead.txt +4 -0
- data/examples/holiday/signature.txt +9 -0
- data/examples/phishing/letter.txt +29 -0
- data/examples/phishing/letterhead.txt +4 -0
- data/examples/phishing/phishing.rb +21 -0
- data/examples/phishing/signature.txt +9 -0
- data/examples/profile/profile.rb +46 -0
- data/lib/bijou.rb +15 -0
- data/lib/bijou/backend.rb +542 -0
- data/lib/bijou/cgi/adapter.rb +201 -0
- data/lib/bijou/cgi/handler.rb +5 -0
- data/lib/bijou/cgi/request.rb +37 -0
- data/lib/bijou/common.rb +12 -0
- data/lib/bijou/component.rb +108 -0
- data/lib/bijou/config.rb +60 -0
- data/lib/bijou/console/adapter.rb +167 -0
- data/lib/bijou/console/handler.rb +4 -0
- data/lib/bijou/console/request.rb +26 -0
- data/lib/bijou/context.rb +431 -0
- data/lib/bijou/diagnostics.rb +87 -0
- data/lib/bijou/errorformatter.rb +322 -0
- data/lib/bijou/exception.rb +39 -0
- data/lib/bijou/filters.rb +107 -0
- data/lib/bijou/httprequest.rb +108 -0
- data/lib/bijou/httpresponse.rb +268 -0
- data/lib/bijou/lexer.rb +513 -0
- data/lib/bijou/minicgi.rb +159 -0
- data/lib/bijou/parser.rb +1026 -0
- data/lib/bijou/processor.rb +404 -0
- data/lib/bijou/prstringio.rb +400 -0
- data/lib/bijou/webrick/adapter.rb +174 -0
- data/lib/bijou/webrick/handler.rb +32 -0
- data/lib/bijou/webrick/request.rb +45 -0
- data/script/cgi.rb +25 -0
- data/script/console.rb +7 -0
- data/script/server.rb +7 -0
- data/test/t1.cfg +5 -0
- data/test/tc_config.rb +26 -0
- data/test/tc_filter.rb +25 -0
- data/test/tc_lexer.rb +120 -0
- data/test/tc_response.rb +103 -0
- data/test/tc_ruby.rb +62 -0
- data/test/tc_stack.rb +50 -0
- 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
|