PrettyException 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ - 0.9.5
2
+ + Removed dependency on mw-template, now code uses rdoc/template instead.
3
+ Patch by Michael Neumann, thanks man.
4
+ - 0.9.3
5
+ + Now properly escapes HTML characters inside code (reported by Patrick Gundlach)
6
+ + Fixed source line counting issue with heredoc strings (reported by Patrick Gundlach)
7
+ + Fixed crash with errors raised near the end of file (reported by Mikael Brockman)
8
+ - 0.9.2
9
+ + Ruby sources are inserted in the backtrace table
10
+ + Added source highlightning based on Simon Strandgaard's Ruby lexer
11
+ + Fixed bug with Mozilla rendering engine
12
+ + Fixed license to be LGPL
@@ -0,0 +1,1091 @@
1
+ ###################################################
2
+ # # # # # # # # # # # # # # # # # # # # # # # # # #
3
+ #
4
+ # = lib/PrettyException.rb
5
+ #
6
+ # PrettyPrint for Exceptions
7
+ #
8
+ # Author:: Dmitry V. Sabanin <sdmitry@lrn.ru>
9
+ # Revision:: $Date: 2004/12/20 05:11:50 $ $Revision: 1.16 $
10
+ # License:: LGPL
11
+ #
12
+ # Modified to use rdoc's templating by Michael Neumann (mneumann@ntecs.de).
13
+ #
14
+ # # # # # # # # # # # # # # # # # # # # # # # # # #
15
+ ###################################################
16
+
17
+ require 'rdoc/template'
18
+ require 'rbconfig'
19
+ require 'cgi'
20
+
21
+ ###################################################
22
+ # Web PrettyPrint for Exceptions
23
+ #
24
+ class PrettyException
25
+
26
+ attr_writer :message
27
+
28
+ ###################################################
29
+ def initialize(exception, tplpath = nil)
30
+ @tplpath = tplpath
31
+ @exception = exception
32
+ @message = nil
33
+ end
34
+ ###################################################
35
+
36
+ ###################################################
37
+ def build_template
38
+ tmpl =
39
+ if @tplpath and File.exists?(@tplpath)
40
+ File.read(@tplpath)
41
+ else
42
+ tplfile = File.readlines(__FILE__)
43
+ start = nil
44
+ tplfile.each_with_index do |line,idx|
45
+ if line =~ /^__END__\s*$/
46
+ start = idx + 1
47
+ break
48
+ end
49
+ end
50
+ tplfile[start..-1].join('')
51
+ end
52
+
53
+ return TemplatePage.new(tmpl)
54
+ end
55
+ ###################################################
56
+
57
+ ###################################################
58
+ def gen_source(file, line, mark_line = true)
59
+ source = File.readlines(file)
60
+ source = hilite_source(source)
61
+ line = line.to_i
62
+ if mark_line
63
+ show = 3
64
+ begin_from = line - show
65
+ end_at = line + show
66
+ begin_from = begin_from > 0 ? begin_from : 0
67
+ work_with = source[begin_from..end_at]
68
+ else
69
+ work_with = [ source[line-1] ]
70
+ end
71
+ work_with ||= []
72
+ res = []
73
+ work_with.each do |i, l|
74
+ l = ('<span class="hl_lineno">%.3d:</span>%s' % [i," "]) + l
75
+ if i == line and mark_line
76
+ buf = '<div class="current_line">' + l + '</div>'
77
+ else
78
+ buf = l + "\n"
79
+ end
80
+ res << buf
81
+ end
82
+ res.join
83
+ end
84
+ ###################################################
85
+
86
+ ###################################################
87
+ def hilite_source(src)
88
+ lx = LexerRuby::LexerOld.new
89
+ src.each do |sline|
90
+ lx.lex_line(sline)
91
+ end
92
+ res = []
93
+ lineno = 0
94
+ this_line = []
95
+ hc = false
96
+ heredoc_buf = nil
97
+ lx.result.each do |text, token|
98
+ if hc
99
+ lineno += 1
100
+ res << [lineno, this_line.join]
101
+ this_line = []
102
+ hc = false
103
+ end
104
+ if token != :heredoc and heredoc_buf and token != :any
105
+ heredoc = heredoc_buf.split(/\n/)
106
+ heredoc.each do |hd_line|
107
+ res << [lineno, ('<span class="hl_heredoc">%s</span>' % [hd_line.to_s])]
108
+ lineno += 1
109
+ end
110
+ this_line = []
111
+ heredoc_buf = nil
112
+ end
113
+ case token
114
+ when :keyword, :ident, :punct,
115
+ :comment, :ivar, :dot,
116
+ :string, :command, :number,
117
+ :gvar, :literal, :symbol
118
+ if token == :comment
119
+ hc = true
120
+ end
121
+ text = CGI.escapeHTML(text)
122
+ this_line << ('<span class="hl_%s">%s</span>' % [token.to_s, text.rstrip])
123
+ when :heredoc
124
+ text = CGI.escapeHTML(text)
125
+ heredoc_buf ||= ''
126
+ heredoc_buf << text
127
+ when :any
128
+ if text =~ /^\s*\n$/
129
+ lineno += 1
130
+ res << [lineno, this_line.join]
131
+ this_line = []
132
+ else
133
+ this_line << text
134
+ end
135
+ else
136
+ this_line << CGI.escapeHTML(text)
137
+ end
138
+ end
139
+ res << [lineno+1, this_line.join] if this_line.length > 0
140
+ res
141
+ end
142
+ ###################################################
143
+
144
+ ###################################################
145
+ def print
146
+ tpl = build_template
147
+ contents = {}
148
+ contents['message'] = CGI::escapeHTML(@exception.message).to_s
149
+ contents['exception'] = @exception.class.to_s
150
+ contents['time'] = Time.now.to_s
151
+ bt = []
152
+ @exception.backtrace.each_with_index do |str,idx|
153
+ file,text = str.scan(/^(.+):(.+)(?::(.+))?$/).flatten;
154
+ file,line = file.split(/:/)
155
+ unless line
156
+ line = text
157
+ text = nil
158
+ end
159
+ is_stdlib = false
160
+ paths = [Config::CONFIG['rubylibdir'], Config::CONFIG['sitedir']]
161
+ paths.each do |dir|
162
+ is_stdlib = true if (file =~ /#{Regexp::escape(dir)}/)
163
+ end
164
+ data = { 'file' => file, 'line' => line, 'text' => text, 'from_stdlib' => is_stdlib }
165
+ if idx == 0
166
+ data['source'] = gen_source(file, line)
167
+ else
168
+ data['source'] = gen_source(file, line)
169
+ end
170
+ data['iteration_id'] = idx.to_s
171
+ bt << data
172
+ end
173
+ contents['backtrace'] = bt
174
+ tpl.write_html_on(output='', contents)
175
+ output
176
+ end
177
+ ###################################################
178
+
179
+ alias_method :to_s, :print
180
+
181
+ end
182
+
183
+ class LexerBase
184
+ def initialize
185
+ @states = []
186
+ @result = []
187
+ @result_endofline = nil
188
+ end
189
+ attr_reader :states, :result, :result_endofline
190
+
191
+ def set_states(states)
192
+ @states = states
193
+ end
194
+ def set_result(result)
195
+ @result = result
196
+ end
197
+ def format(text, state_output)
198
+ @result << [text, state_output]
199
+ end
200
+ def format_end(state_output)
201
+ @result_endofline = state_output
202
+ end
203
+ def match(regexp, output)
204
+ m = regexp.match(@text)
205
+ return false unless m
206
+ txt = @text.slice!(0, m.end(0))
207
+ format(txt, output)
208
+ true
209
+ end
210
+ def lex_line(text)
211
+ raise "derived class #{self.class} must overload #lex_line."
212
+ end
213
+ def self.profile
214
+ lines = IO.readlines(__FILE__)
215
+ lexer = self.new
216
+ puts "profiling the #{self.inspect} lexer (this may take some time)"
217
+ require 'profiler'
218
+ Profiler__.start_profile
219
+ lines.each do |line|
220
+ lexer.set_states([])
221
+ lexer.set_result([])
222
+ lexer.lex_line(line)
223
+ end
224
+ Profiler__.print_profile(STDOUT)
225
+ end
226
+ def self.benchmark
227
+ n = 10000
228
+ puts "benchmarking the lexers (computing #{n} lines " +
229
+ "with GC disabled)"
230
+ require 'benchmark'
231
+ Benchmark.bm(20) do |b|
232
+ lexer = LexerRuby::LexerOld.new
233
+ #=begin
234
+ lines = IO.readlines(__FILE__)
235
+ GC.disable
236
+ b.report("#{lexer.class}") do
237
+ n.times do |i|
238
+ lexer.set_states([])
239
+ lexer.set_result([])
240
+ lexer.lex_line(lines[i%lines.size].clone)
241
+ end
242
+ end
243
+ #=begin
244
+ GC.enable
245
+ GC.start
246
+ lines = IO.readlines(__FILE__)
247
+ lexer = LexerRuby::LexerNew.new
248
+ GC.disable
249
+ b.report("#{lexer.class}") do
250
+ n.times do |i|
251
+ lexer.set_states([])
252
+ lexer.set_result([])
253
+ lexer.lex_line(lines[i%lines.size])
254
+ end
255
+ end
256
+ =begin
257
+ =end
258
+ GC.enable
259
+ GC.start
260
+ lines = IO.readlines(__FILE__)
261
+ lexer = LexerRuby::Lexer3.new
262
+ GC.disable
263
+ b.report("#{lexer.class}") do
264
+ n.times do |i|
265
+ lexer.set_states([])
266
+ lexer.set_result([])
267
+ lexer.lex_line(lines[i%lines.size])
268
+ end
269
+ end
270
+ GC.enable
271
+ end
272
+ end
273
+ end
274
+
275
+ module LexerText
276
+
277
+ class Lexer < LexerBase
278
+ RE_TAB = /\A\t+/
279
+ RE_NOTTAB = /\A[^\t]+/
280
+ def lex_line(text)
281
+ @text = text
282
+ until @text.empty?
283
+ if match(RE_TAB, :tab)
284
+ else
285
+ match(RE_NOTTAB, :text)
286
+ end
287
+ end
288
+ end
289
+ end # class Lexer
290
+
291
+ end # module LexerText
292
+
293
+ module LexerRuby
294
+
295
+ module State
296
+
297
+ class Base
298
+ end
299
+
300
+ class Heredoc < Base
301
+ def initialize(begin_tag, ignore_leading_spaces, interpolate=true)
302
+ @begin_tag = begin_tag
303
+ @ignore_leading_spaces = ignore_leading_spaces
304
+ @interpolate = interpolate
305
+ end
306
+ attr_reader :begin_tag, :ignore_leading_spaces, :interpolate
307
+ def ==(other)
308
+ (self.class == other.class) and
309
+ (@begin_tag == other.begin_tag) and
310
+ (@ignore_leading_spaces == other.ignore_leading_spaces) and
311
+ (@interpolate == other.interpolate)
312
+ end
313
+ end
314
+
315
+ class Comment < Base
316
+ def ==(other)
317
+ (self.class == other.class)
318
+ end
319
+ end
320
+
321
+ class Endoffile < Base
322
+ def ==(other)
323
+ (self.class == other.class)
324
+ end
325
+ end
326
+
327
+ end # module State
328
+
329
+ class NewerRubyLexer
330
+ RE_TOKENIZE = Regexp.new([
331
+ # TODO: @ivar
332
+ # TODO: @@cvar
333
+ # TODO: $gvar
334
+ # TODO: %literals %w(a b c) %Q|'"|
335
+ # TODO: :symbol
336
+ # TODO: ?x chars
337
+ # TODO: 0b01001 binary data
338
+ # TODO: 0x234af hex data
339
+ # TODO: keywords =begin defined?
340
+ # TODO: ruby puncturation .. ... && ||
341
+ # TODO: /regexp/
342
+ # TODO: __END__ tag
343
+ # TODO: illegal ruby heredoc which has space after end-tag
344
+ # TODO: illegal ruby puncturation &&& |||
345
+ # TODO: illegal ruby numbers 0x23Yab
346
+ '#.*', # # blah ## !?! comment
347
+ '<<-?[[:alpha:]]+', # <<HTML <<-XML heredoc
348
+ '<<-?\'[[:alpha:]]+\'', # <<'eof' <<-'X' heredoc single quoted
349
+ '<<-?"[[:alpha:]]+"', # <<"eof" <<-"X" heredoc double quoted
350
+ '\.[[:alpha:]][[:alnum:]_]*', # .method .dup2 method call
351
+ '[[:alpha:]][[:alnum:]_]*', # value pix2_3 identifier
352
+ '\'(?:[^\\\\]|\\\\.)*?\'', # '\'x\\' '' string single quoted
353
+ '"(?:[^\\\\]|\\\\.)*?"', # "ab" "" string double quoted
354
+ '\d+\.\d+', # 0.123 32.10 number as float
355
+ '\d+', # 42 999 number as integer
356
+ '.' # * + fallthrough
357
+ ].join('|'))
358
+ def initialize
359
+ @char_to_symbol_hash = {
360
+ " " => :space,
361
+ "\t" => :tab
362
+ }
363
+ @char_to_symbol_hash.default = :should_not_happen
364
+ end
365
+ def tokenize(string)
366
+ string.scan(RE_TOKENIZE)
367
+ end
368
+ def lex(string)
369
+ tokens = tokenize(string)
370
+ states = tokens.map do |token|
371
+ # TODO: by inserting 2 parentesises in the TOKENIZER
372
+ # then I can destinguish between good/bad tokens.
373
+ # 'ab'.scan(/(a)(b)/) do |(good, bad)|
374
+ case token
375
+ when /\A(?:"|')./
376
+ :string
377
+ when /\A<<-?(?:"|'|)[[:alpha:]]/
378
+ :heredoc
379
+ when /\A\.[[:alpha:]]/
380
+ :method
381
+ when /\A[[:alpha:]]/
382
+ :ident
383
+ when /\A#/
384
+ :comment
385
+ when /\A[[:punct:]]/
386
+ :punct
387
+ when /\A[[:digit:]]/
388
+ :number
389
+ else
390
+ @char_to_symbol_hash[token]
391
+ end
392
+ end
393
+ [tokens, states]
394
+ end
395
+ end
396
+
397
+ class Lexer3 < LexerBase
398
+ def initialize
399
+ @rl = NewerRubyLexer.new
400
+ super
401
+ end
402
+ def what_to_output(hash)
403
+ @rl.what_to_output(hash)
404
+ end
405
+ def lex_line(text)
406
+ tokens, states = @rl.lex(text)
407
+ tokens.each_with_index do |token, index|
408
+ format(token, states[index])
409
+ end
410
+ true
411
+ end
412
+ end
413
+
414
+
415
+ class RubyLexer
416
+ def initialize
417
+ @result = []
418
+ end
419
+ attr_reader :result
420
+ PUNCT = ['(', ')'] +
421
+ %w(=== == =~ => = != !~ !) +
422
+ %w(<< <=> <= < >= >) +
423
+ %w({ } [ ]) +
424
+ %w(:: : ... ..) +
425
+ %w(+= + -= - ** * / %) +
426
+ %w(|| | && &) +
427
+ %w(, ;)
428
+ RE_NUMBER = /\d[\d\.]*/
429
+ RE_PUNCT = Regexp.new('(?:' +
430
+ PUNCT.map{|i| Regexp.escape(i)}.join('|') + ')')
431
+ RE_IDENT = /[[:alpha:]][\w\!]*/
432
+ RE_STRING = /".*?"|'.*?'/
433
+ RE_COMMENT = /#.*/
434
+ RE_COMMAND = /\.[[:alnum:]_]*[[:alnum:]_\?\!]/
435
+ RE_SPACE = /\x20+/
436
+ RE_NEWLINE = /\n+/
437
+ RE_TABS = /\t+/
438
+ def scan_index(string, regexp, prio, symbol)
439
+ string.scan(regexp) do
440
+ @result << [$~.begin(0), prio, $~.end(0), symbol]
441
+ end
442
+ end
443
+ def lex(string)
444
+ scan_index(string, RE_COMMENT, 0, :comment)
445
+ scan_index(string, RE_STRING, 1, :string)
446
+ scan_index(string, RE_COMMAND, 2, :command)
447
+ scan_index(string, RE_IDENT, 3, :ident)
448
+ scan_index(string, RE_NUMBER, 4, :number)
449
+ scan_index(string, RE_PUNCT, 5, :punct)
450
+ scan_index(string, RE_SPACE, 6, :space)
451
+ scan_index(string, RE_NEWLINE, 7, :newline)
452
+ scan_index(string, RE_TABS, 8, :tabs)
453
+ end
454
+ def result
455
+ # primary key = string begin position
456
+ # secondary key = priority
457
+ @result.sort!
458
+ # collect the highest precedens data
459
+ ary = [0]
460
+ @result.each do |i1, prio, i2, symbol|
461
+ next if ary.last > i1 # discard low precedens data
462
+ if ary.last < i1
463
+ ary << :any
464
+ ary << i1
465
+ end
466
+ ary << symbol
467
+ ary << i2
468
+ end
469
+ #p ary
470
+ ary
471
+ end
472
+ def self.mk(*symbols)
473
+ symbols.each do |symbol|
474
+ class_eval %{
475
+ def #{symbol.to_s}
476
+ res = []
477
+ @result.each do |i1, prio, i2, sym|
478
+ if sym == :#{symbol.to_s}
479
+ res << [i1, i2]
480
+ end
481
+ end
482
+ res
483
+ end
484
+ }
485
+ end
486
+ end
487
+ mk :number, :ident, :string, :punct
488
+ mk :comment, :command, :space, :newline, :tabs
489
+ end
490
+
491
+ class LexerNew < LexerBase
492
+ def lex_line(text)
493
+ #puts("-"*40)
494
+ #puts "text=#{text.inspect}"
495
+ rl = RubyLexer.new
496
+ rl.lex(text)
497
+ res = rl.result
498
+ #puts "res=#{res.inspect}"
499
+ i1 = res.shift
500
+ while res.size > 1
501
+ symbol = res.shift
502
+ i2 = res.shift
503
+ format(text[i1, (i2-i1)], symbol)
504
+ i1 = i2
505
+ end
506
+ #puts "result=#{@result.inspect}"
507
+ true
508
+ end
509
+ end
510
+
511
+ class LexerOld < LexerBase
512
+ RE_COMMENT = /^#.*/m
513
+
514
+ RE_TAB = /^\t+/
515
+
516
+ RE_SPACE = /^\x20+/
517
+
518
+ KEYWORDS = %w(alias and begin BEGIN break case class) +
519
+ %w(defined? def do else elsif end END ensure for if loop) +
520
+ %w(module next nil not or raise redo require rescue) +
521
+ %w(retry return self super then true false undef) +
522
+ %w(unless until yield when while)
523
+ RE_KEYWORD = Regexp.new(
524
+ '\A(?:' +
525
+ KEYWORDS.map{|txt|Regexp.escape(txt)}.join('|') +
526
+ ')(?!\w)'
527
+ )
528
+
529
+ RE_SYMBOL = /\A:[[:alpha:]_][[:alnum:]_]*/
530
+
531
+ RE_STRING = /^("|\')(?:[^\\]|\\.)*?\1/
532
+ RE_STRING_INTERPOL = /\A ((?:[^\\]|\\.)*?) (\#\{ .*? \}) /x
533
+ # TODO: interpolated code can nest (hint: recursion is necessary)
534
+ def match_string
535
+ m = RE_STRING.match(@text)
536
+ return false unless m
537
+ txt = @text.slice!(0, m.end(0))
538
+ if m[1] == '\''
539
+ format(txt, :string)
540
+ return true
541
+ end
542
+ # double quoted strings may contain interpolated code
543
+ until txt.empty?
544
+ m = RE_STRING_INTERPOL.match(txt)
545
+ unless m
546
+ format(txt, :string)
547
+ break
548
+ end
549
+ format(m[1], :string) unless m[1].empty?
550
+ format(m[2], :string1)
551
+ txt.slice!(0, m.end(0))
552
+ end
553
+ true
554
+ end
555
+
556
+ RE_REGEXP = /\A\/(.*?[^\\])?\//
557
+
558
+ RE_IVAR = /\A@[[:alnum:]_]+/
559
+
560
+ RE_DOT = /\A\.[[:alnum:]_]*[[:alnum:]_\?\!]/
561
+
562
+ RE_IDENTIFIER = /\A(?:[[:alnum:]_]+|\S+)/
563
+
564
+ RE_NUMBER = Regexp.new(
565
+ '\A(?:' + [
566
+ '0x[_a-fA-F0-9]+',
567
+ '0b[_01]+',
568
+ '\d[0-9_]*(?:\.[0-9_]*)?',
569
+ '\?.'
570
+ ].join('|') +
571
+ ')'
572
+ )
573
+
574
+ PUNCT = ['(', ')'] +
575
+ %w(=== == =~ => = != !~ !) +
576
+ %w(<< <=> <= < >= >) +
577
+ %w({ } [ ]) +
578
+ %w(:: : ... ..) +
579
+ %w(+= + -= - ** * / %) +
580
+ %w(|| | && &) +
581
+ %w(, ;)
582
+ RE_PUNCT = Regexp.new(
583
+ '\A(?:' +
584
+ PUNCT.map{|txt|Regexp.escape(txt)}.join('|') +
585
+ ')'
586
+ )
587
+
588
+ VAR_GLOBALS = %q(_~*$!@/\\;,.=:<>"-&`'+1234567890).split(//)
589
+ RE_GVAR = Regexp.new(
590
+ '\A\$(?:' +
591
+ VAR_GLOBALS.map{|txt|Regexp.escape(txt)}.join('|') +
592
+ '|[[:alnum:]_]+' +
593
+ ')'
594
+ )
595
+
596
+ # TODO: deal with multiline literals
597
+ RE_LITERAL = Regexp.new(
598
+ '\A%[Qqwrx]?(?:' + [
599
+ '\(.*?\)', # TODO: must count pairs
600
+ '\{.*?\}', # TODO: must count pairs
601
+ '\<.*?\>', # TODO: must count pairs
602
+ '\[.*?\]', # TODO: must count pairs
603
+ '([^\(\{\<\[]).*?\1'
604
+ ].join('|') + ')'
605
+ )
606
+
607
+ RE_BEGIN = /\A=begin$\n?\x20*\z/ # eat tailing space
608
+ def match_comment_begin
609
+ m = RE_BEGIN.match(@text)
610
+ return false unless m
611
+ #puts "comments"
612
+ @states << State::Comment.new
613
+ txt = @text.slice!(0, m.end(0))
614
+ format(txt, :mcomment)
615
+ format_end(:mcomment_end)
616
+ true
617
+ end
618
+ RE_HEREDOC = /\A<<(-)?('|"|)(\w+)\2/
619
+ def match_heredoc_begin
620
+ m = RE_HEREDOC.match(@text)
621
+ return false unless m
622
+ ignore_leading_space = (m[1] != nil)
623
+ interpolate = (m[2] != "'")
624
+ begin_pattern = m[3]
625
+ @states << State::Heredoc.new(
626
+ begin_pattern,
627
+ ignore_leading_space,
628
+ interpolate
629
+ )
630
+ txt = @text.slice!(0, m.end(0))
631
+ format(txt, :heredoc)
632
+ true
633
+ end
634
+ RE_END = /\A__END__$\n?\x20*\z/ # eat tailing space
635
+ def match_endoffile
636
+ m = RE_END.match(@text)
637
+ return false unless m
638
+ #puts "propagate __END__"
639
+ @states << State::Endoffile.new
640
+ txt = @text.slice!(0, m.end(0))
641
+ format(txt, :endoffile)
642
+ format_end(:endoffile_end)
643
+ true
644
+ end
645
+ def lex_line_normal(text)
646
+ @text = text
647
+ return if match_comment_begin
648
+ return if match_endoffile
649
+ until @text.empty?
650
+ if match(RE_COMMENT, :comment)
651
+ format_end(:comment_end)
652
+ elsif match(RE_REGEXP, :regexp)
653
+ elsif match_heredoc_begin
654
+ elsif match(RE_LITERAL, :literal)
655
+ elsif match(RE_KEYWORD, :keyword)
656
+ elsif match(RE_SYMBOL, :symbol)
657
+ elsif match(RE_PUNCT, :punct)
658
+ elsif match(RE_GVAR, :gvar)
659
+ elsif match_string
660
+ elsif match(RE_NUMBER, :number)
661
+ elsif match(RE_IVAR, :ivar)
662
+ elsif match(RE_DOT, :dot)
663
+ elsif match(RE_IDENTIFIER, :ident)
664
+ elsif match(RE_TAB, :tab)
665
+ elsif match(RE_SPACE, :space)
666
+ else
667
+ #@text.slice!(0, 1)
668
+ txt = @text.slice!(0, 1)
669
+ format(txt, :any)
670
+ end
671
+ end
672
+ end
673
+ def match_heredoc_end(regexp)
674
+ m = regexp.match(@text)
675
+ return false unless m
676
+ #puts "end of heredoc"
677
+ @states.shift
678
+ txt = @text.slice!(0, m.end(0))
679
+ format(txt, :heredoc)
680
+ format_end(:heredoc_end2)
681
+ true
682
+ end
683
+ def lex_line_heredoc(text)
684
+ # TODO: color interpolated code #{code}
685
+ @text = text
686
+ format_end(:heredoc_end)
687
+ hd_end = nil
688
+ state = @states[0]
689
+ hd_end = /\A#{state.begin_tag}$\n?\x20*\z/
690
+ return if match_heredoc_end(hd_end)
691
+ # continue lexing
692
+ ign_lead_spc = state.ignore_leading_spaces
693
+ until @text.empty?
694
+ if match(RE_TAB, :heredoc_tab)
695
+ elsif ign_lead_spc and match_heredoc_end(hd_end)
696
+ else
697
+ txt = @text.slice!(0, 1)
698
+ format(txt, :heredoc)
699
+ end
700
+ end
701
+ end
702
+ def match_comment_end
703
+ m = /\A\=end\b.*?$\n?\x20*\z/.match(@text)
704
+ return false unless m
705
+ #puts "comment end"
706
+ @states.shift
707
+ txt = @text.slice!(0, m.end(0))
708
+ format(txt, :mcomment)
709
+ true
710
+ end
711
+ def lex_line_comment(text)
712
+ @text = text
713
+ format_end(:mcomment_end)
714
+ return if match_comment_end
715
+ until @text.empty?
716
+ if match(RE_TAB, :mcomment_tab)
717
+ else
718
+ txt = @text.slice!(0, 1)
719
+ format(txt, :mcomment)
720
+ end
721
+ end
722
+ end
723
+ def lex_line_endoffile(text)
724
+ @text = text
725
+ format_end(:endoffile_end)
726
+ until @text.empty?
727
+ if match(RE_TAB, :endoffile_tab)
728
+ else
729
+ txt = @text.slice!(0, 1)
730
+ format(txt, :endoffile)
731
+ end
732
+ end
733
+ end
734
+ def lex_line(text)
735
+ if @states.empty?
736
+ return lex_line_normal(text)
737
+ end
738
+ state = @states[0]
739
+ case state
740
+ when State::Heredoc: lex_line_heredoc(text)
741
+ when State::Comment: lex_line_comment(text)
742
+ when State::Endoffile: lex_line_endoffile(text)
743
+ else
744
+ raise "unknown state #{state.class}"
745
+ end
746
+ end
747
+ end
748
+
749
+ # TODO: make the new lexer work!
750
+ Lexer = LexerOld # slow
751
+ #Lexer = LexerNew # slow
752
+ #Lexer = Lexer3 # fastest
753
+
754
+ end # module LexerRuby
755
+
756
+ __END__
757
+ <html>
758
+ <head>
759
+ <title>Oops!</title>
760
+ <style>
761
+ .data {
762
+ border-style: dotted;
763
+ padding: 4px; }
764
+ .trace_header {
765
+ border-style: dotted;
766
+ border-width: thin;
767
+ background-color: #CCCCCC;
768
+ text-align: center; }
769
+ .normal_trace_entry {
770
+ border-style: dotted;
771
+ border-width: thin;
772
+ text-align: center;
773
+ padding: 6px; }
774
+ .stdlib_trace_entry {
775
+ border-style: dotted;
776
+ border-width: thin;
777
+ text-align: right;
778
+ padding: 6px; }
779
+ .source {
780
+ width: 100%;
781
+ background-color: #F4F4F4;
782
+ display: none;
783
+ }
784
+ span.hl_lineno {
785
+ font-weight: bold;
786
+ }
787
+ pre {
788
+ width: 80%;
789
+ padding: 0px;
790
+ margin: 0px;
791
+ }
792
+ div.current_line {
793
+ color: red;
794
+ background-color: #F4DADA;
795
+ }
796
+ span.hl_keyword {
797
+ font-weight: bold;
798
+ }
799
+ span.hl_punct {
800
+ font-weight: bold;
801
+ color: darkblue;
802
+ }
803
+ span.hl_ident {
804
+ }
805
+ span.hl_command {
806
+ font-weight: bold;
807
+ }
808
+ span.hl_number {
809
+ color: darkgreen;
810
+ }
811
+ span.hl_string {
812
+ color: darkgreen;
813
+ }
814
+ span.hl_comment {
815
+ color: grey;
816
+ }
817
+ span.hl_ivar {
818
+ font-weight: bold;
819
+ color: darkred;
820
+ }
821
+ span.hl_dot {
822
+ font-weight: bold;
823
+ }
824
+ span.hl_literal {
825
+ color: green;
826
+ }
827
+ span.hl_gvar {
828
+ font-weight: bold;
829
+ }
830
+ span.hl_symbol {
831
+ color: blue;
832
+ }
833
+ span.hl_regexp {
834
+ color: green;
835
+ }
836
+ tr {
837
+ background-color: white;
838
+ }
839
+ </style>
840
+ <script type="text/javascript" language="javascript">
841
+ function toggleCode( id ) {
842
+ if ( document.getElementById )
843
+ elem = document.getElementById( id );
844
+ else if ( document.all )
845
+ elem = eval( "document.all." + id );
846
+ else
847
+ return false;
848
+
849
+ elemStyle = elem.style;
850
+
851
+ if ( elemStyle.display != "block" ) {
852
+ elemStyle.display = "block"
853
+ } else {
854
+ elemStyle.display = "none"
855
+ }
856
+
857
+ return true;
858
+ }
859
+
860
+ var isDOM = (typeof(document.getElementsByTagName) != 'undefined'
861
+ && typeof(document.createElement) != 'undefined')
862
+ ? 1 : 0;
863
+ var isIE4 = (typeof(document.all) != 'undefined'
864
+ && parseInt(navigator.appVersion) >= 4)
865
+ ? 1 : 0;
866
+ var isNS4 = (typeof(document.layers) != 'undefined')
867
+ ? 1 : 0;
868
+ var capable = (isDOM || isIE4 || isNS4)
869
+ ? 1 : 0;
870
+ // Uggly fix for Opera and Konqueror 2.2 that are half DOM compliant
871
+ if (capable) {
872
+ if (typeof(window.opera) != 'undefined') {
873
+ var browserName = ' ' + navigator.userAgent.toLowerCase();
874
+ if ((browserName.indexOf('konqueror 7') == 0)) {
875
+ capable = 0;
876
+ }
877
+ } else if (typeof(navigator.userAgent) != 'undefined') {
878
+ var browserName = ' ' + navigator.userAgent.toLowerCase();
879
+ if ((browserName.indexOf('konqueror') > 0) && (browserName.indexOf('konqueror/3') == 0)) {
880
+ capable = 0;
881
+ }
882
+ } // end if... else if...
883
+ } // end if
884
+
885
+ /**
886
+ * This array is used to remember mark status of rows in browse mode
887
+ */
888
+ var marked_row = new Array;
889
+
890
+
891
+ /**
892
+ * Sets/unsets the pointer and marker in browse mode
893
+ *
894
+ * @param object the table row
895
+ * @param integer the row number
896
+ * @param string the action calling this script (over, out or click)
897
+ * @param string the default background color
898
+ * @param string the color to use for mouseover
899
+ * @param string the color to use for marking a row
900
+ *
901
+ * @return boolean whether pointer is set or not
902
+ */
903
+ function setPointer(theRow, theRowNum, theAction, theDefaultColor, thePointerColor, theMarkColor)
904
+ {
905
+ var theCells = null;
906
+
907
+ // 1. Pointer and mark feature are disabled or the browser can't get the
908
+ // row -> exits
909
+ if ((thePointerColor == '' && theMarkColor == '')
910
+ || typeof(theRow.style) == 'undefined') {
911
+ return false;
912
+ }
913
+
914
+ // 2. Gets the current row and exits if the browser can't get it
915
+ if (typeof(document.getElementsByTagName) != 'undefined') {
916
+ theCells = theRow.getElementsByTagName('td');
917
+ }
918
+ else if (typeof(theRow.cells) != 'undefined') {
919
+ theCells = theRow.cells;
920
+ }
921
+ else {
922
+ return false;
923
+ }
924
+
925
+ // 3. Gets the current color...
926
+ var rowCellsCnt = theCells.length;
927
+ var domDetect = null;
928
+ var currentColor = null;
929
+ var newColor = null;
930
+ // 3.1 ... with DOM compatible browsers except Opera that does not return
931
+ // valid values with "getAttribute"
932
+ if (typeof(window.opera) == 'undefined'
933
+ && typeof(theCells[0].getAttribute) != 'undefined') {
934
+ currentColor = theCells[0].getAttribute('bgcolor');
935
+ domDetect = true;
936
+ }
937
+ // 3.2 ... with other browsers
938
+ else {
939
+ currentColor = theCells[0].style.backgroundColor;
940
+ domDetect = false;
941
+ } // end 3
942
+
943
+ // 3.3 ... Opera changes colors set via HTML to rgb(r,g,b) format so fix it
944
+ if (currentColor.indexOf("rgb") >= 0)
945
+ {
946
+ var rgbStr = currentColor.slice(currentColor.indexOf('(') + 1,
947
+ currentColor.indexOf(')'));
948
+ var rgbValues = rgbStr.split(",");
949
+ currentColor = "#";
950
+ var hexChars = "0123456789ABCDEF";
951
+ for (var i = 0; i < 3; i++)
952
+ {
953
+ var v = rgbValues[i].valueOf();
954
+ currentColor += hexChars.charAt(v/16) + hexChars.charAt(v%16);
955
+ }
956
+ }
957
+
958
+ // 4. Defines the new color
959
+ // 4.1 Current color is the default one
960
+ if (currentColor == ''
961
+ || currentColor.toLowerCase() == theDefaultColor.toLowerCase()) {
962
+ if (theAction == 'over' && thePointerColor != '') {
963
+ newColor = thePointerColor;
964
+ }
965
+ else if (theAction == 'click' && theMarkColor != '') {
966
+ newColor = theMarkColor;
967
+ marked_row[theRowNum] = true;
968
+ // Garvin: deactivated onclick marking of the checkbox because it's also executed
969
+ // when an action (like edit/delete) on a single item is performed. Then the checkbox
970
+ // would get deactived, even though we need it activated. Maybe there is a way
971
+ // to detect if the row was clicked, and not an item therein...
972
+ // document.getElementById('id_rows_to_delete' + theRowNum).checked = true;
973
+ }
974
+ }
975
+ // 4.1.2 Current color is the pointer one
976
+ else if (currentColor.toLowerCase() == thePointerColor.toLowerCase()
977
+ && (typeof(marked_row[theRowNum]) == 'undefined' || !marked_row[theRowNum])) {
978
+ if (theAction == 'out') {
979
+ newColor = theDefaultColor;
980
+ }
981
+ else if (theAction == 'click' && theMarkColor != '') {
982
+ newColor = theMarkColor;
983
+ marked_row[theRowNum] = true;
984
+ // document.getElementById('id_rows_to_delete' + theRowNum).checked = true;
985
+ }
986
+ }
987
+ // 4.1.3 Current color is the marker one
988
+ else if (currentColor.toLowerCase() == theMarkColor.toLowerCase()) {
989
+ if (theAction == 'click') {
990
+ newColor = (thePointerColor != '')
991
+ ? thePointerColor
992
+ : theDefaultColor;
993
+ marked_row[theRowNum] = (typeof(marked_row[theRowNum]) == 'undefined' || !marked_row[theRowNum])
994
+ ? true
995
+ : null;
996
+ // document.getElementById('id_rows_to_delete' + theRowNum).checked = false;
997
+ }
998
+ } // end 4
999
+
1000
+ // 5. Sets the new color...
1001
+ if (newColor) {
1002
+ var c = null;
1003
+ // 5.1 ... with DOM compatible browsers except Opera
1004
+ if (domDetect) {
1005
+ for (c = 0; c < rowCellsCnt; c++) {
1006
+ theCells[c].setAttribute('bgcolor', newColor, 0);
1007
+ } // end for
1008
+ }
1009
+ // 5.2 ... with other browsers
1010
+ else {
1011
+ for (c = 0; c < rowCellsCnt; c++) {
1012
+ theCells[c].style.backgroundColor = newColor;
1013
+ }
1014
+ }
1015
+ } // end 5
1016
+
1017
+ return true;
1018
+ } // end of the 'setPointer()' function
1019
+
1020
+
1021
+ /**
1022
+ * getElement
1023
+ */
1024
+ function getElement(e,f){
1025
+ if(document.layers){
1026
+ f=(f)?f:self;
1027
+ if(f.document.layers[e]) {
1028
+ return f.document.layers[e];
1029
+ }
1030
+ for(W=0;i<f.document.layers.length;W++) {
1031
+ return(getElement(e,fdocument.layers[W]));
1032
+ }
1033
+ }
1034
+ if(document.all) {
1035
+ return document.all[e];
1036
+ }
1037
+ return document.getElementById(e);
1038
+ }
1039
+ </script>
1040
+ </head>
1041
+ <body bgcolor="white">
1042
+ <table cellspacing="4" width ="80%" align="center">
1043
+ <tr>
1044
+ <td colspan="3" align="left" class="normal_trace_entry">
1045
+ <p class="data" style="font-size: large; margin: 0px; border-color: red;"><b>Exception raised!</b><br />
1046
+ <b>%exception%</b>: <b>%message%</b><br />
1047
+ Time: <b>%time%</b>
1048
+ </p>
1049
+ </td>
1050
+ </tr>
1051
+ <tr>
1052
+ <td class="trace_header"><b>File</b></td>
1053
+ <td class="trace_header"><b>Line</b></td>
1054
+ <td class="trace_header"><b>Info</b></td>
1055
+ </tr>
1056
+ START:backtrace
1057
+ IFNOT:from_stdlib
1058
+ <tr bgcolor="white" onmouseover="setPointer(this, %iteration_id%, 'over', 'white', '#CCFFCC', '#FFB2B2');" onmouseout="setPointer(this, %iteration_id%, 'out', 'white', '#CCFFCC', '#FFB2B2');" onmousedown="toggleCode('src%iteration_id%'); setPointer(this, %iteration_id%, 'click', 'white', '#CCFFCC', '#FFB2B2');">
1059
+ IF:text
1060
+ <td bgcolor="white" class="normal_trace_entry">%file%</td>
1061
+ <td bgcolor="white" class="normal_trace_entry">%line%</td>
1062
+ <td bgcolor="white" class="normal_trace_entry">%text%</td>
1063
+ ENDIF:text
1064
+ IFNOT:text
1065
+ <td bgcolor="white" class="normal_trace_entry">%file%</td>
1066
+ <td bgcolor="white" class="normal_trace_entry" colspan="2">%line%</td>
1067
+ ENDIF:text
1068
+ IF:source
1069
+ </tr>
1070
+ <tr>
1071
+ <td colspan="3"><div id="src%iteration_id%" class="source"><tt><pre>%source%</pre></tt></div></td>
1072
+ ENDIF:source
1073
+ ENDIF:from_stdlib
1074
+ IF:from_stdlib
1075
+ <tr bgcolor="white" onmouseover="setPointer(this, %iteration_id%, 'over', 'white', '#CCFFCC', '#FFB2B2');" onmouseout="setPointer(this, %iteration_id%, 'out', 'white', '#CCFFCC', '#FFB2B2');" onmousedown="setPointer(this, %iteration_id%, 'click', 'white', '#CCFFCC', '#FFB2B2');">
1076
+ IF:text
1077
+ <td bgcolor="white" class="stdlib_trace_entry">%file%</td>
1078
+ <td bgcolor="white" class="stdlib_trace_entry">%line%</td>
1079
+ <td bgcolor="white" class="stdlib_trace_entry">%text%</td>
1080
+ ENDIF:text
1081
+ IFNOT:text
1082
+ <td bgcolor="white" class="stdlib_trace_entry">%file%</td>
1083
+ <td bgcolor="white" class="stdlib_trace_entry" colspan="2">%line%</td>
1084
+ ENDIF:text
1085
+ ENDIF:from_stdlib
1086
+ </tr>
1087
+
1088
+ END:backtrace
1089
+ </table>
1090
+ </body>
1091
+ </html>
metadata ADDED
@@ -0,0 +1,37 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.1
3
+ specification_version: 1
4
+ name: PrettyException
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.9.5
7
+ date: 2004-12-20
8
+ summary: PrettyException is a library to output pretty html output for raised exceptions.
9
+ require_paths:
10
+ - lib
11
+ author: Dmitry V. Sabanin
12
+ email: sdmitry@muravey.net
13
+ homepage: http://muravey-tools.rubyforge.org
14
+ rubyforge_project: muravey-tools
15
+ description:
16
+ autorequire: PrettyException
17
+ default_executable:
18
+ bindir: bin
19
+ has_rdoc: false
20
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
21
+ requirements:
22
+ -
23
+ - ">"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.0.0
26
+ version:
27
+ platform: ruby
28
+ files:
29
+ - lib/PrettyException.rb
30
+ - PrettyException-CHANGES
31
+ test_files: []
32
+ rdoc_options: []
33
+ extra_rdoc_files: []
34
+ executables: []
35
+ extensions: []
36
+ requirements: []
37
+ dependencies: []