/[Frey]/branches/zimbardo/lib/Frey/Web.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /branches/zimbardo/lib/Frey/Web.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 644 - (hide annotations)
Sun Nov 30 16:21:07 2008 UTC (15 years, 4 months ago) by dpavlin
Original Path: trunk/lib/Frey/Web.pm
File size: 13505 byte(s)
open exit in new target so we can restart Mojo server and just close tab.

Then, we have additional step of reloading the page, but that will be fixed
when we have ability to send stuff directly to browser socket before exit
1 dpavlin 100 package Frey::Web;
2     use Moose::Role;
3    
4 dpavlin 564 with 'Frey::Session';
5    
6 dpavlin 388 use Frey::Types;
7    
8 dpavlin 564 #use Continuity::Widget::DomNode;
9 dpavlin 100 use Data::Dump qw/dump/;
10 dpavlin 518 use Carp qw/confess cluck/;
11 dpavlin 161 use File::Slurp;
12 dpavlin 100
13 dpavlin 410 use Frey::Bookmarklet;
14     use Frey::ClassBrowser;
15 dpavlin 540 use Frey::INC;
16    
17 dpavlin 505 use Frey::SVK;
18 dpavlin 410
19 dpavlin 611 use Text::Tabs; # expand, unexpand
20    
21 dpavlin 578 our @head;
22 dpavlin 584 sub head { @head }
23 dpavlin 121
24 dpavlin 392 has 'request_url' => (
25 dpavlin 388 is => 'rw',
26     isa => 'Uri', coerce => 1,
27 dpavlin 625 required => 1,
28 dpavlin 627 default => sub {
29     cluck "undefined request_url";
30     '/';
31     },
32 dpavlin 388 );
33    
34 dpavlin 418 has 'title' => (
35     is => 'rw',
36     isa => 'Str',
37     lazy => 1,
38     default => sub {
39     my ($self) = @_;
40     ref($self);
41     },
42     );
43    
44 dpavlin 448 has 'content_type' => (
45     is => 'rw',
46     isa => 'Str',
47     default => 'text/html',
48 dpavlin 476 documentation => 'Content-type header',
49 dpavlin 448 );
50    
51 dpavlin 476 has 'dump_max_bytes' => (
52     is => 'rw',
53     isa => 'Int',
54     default => 4096,
55 dpavlin 543 documentation => 'maximum dump size sent to browser before truncation',
56 dpavlin 476 );
57    
58 dpavlin 161 has 'inline_smaller_than' => (
59     is => 'rw',
60     isa => 'Int',
61     default => 10240,
62 dpavlin 543 documentation => 'inline JavaScript and CSS to reduce round-trips',
63 dpavlin 161 );
64    
65 dpavlin 543 has 'html_dump_width' => (
66     documentation => 'crop longer lines in dumps',
67 dpavlin 535 is => 'rw',
68     isa => 'Int',
69 dpavlin 543 # required => 1, # FIXME we can't have required fields with defaults because Frey::Action isn't smart enough and asks for them
70 dpavlin 634 default => 250,
71 dpavlin 535 );
72    
73     my %escape = ('<'=>'&lt;', '>'=>'&gt;', '&'=>'&amp;', '"'=>'&quot;');
74     my $escape_re = join '|' => keys %escape;
75    
76     sub html_escape {
77     my ( $self, $html ) = @_;
78     $html =~ s/($escape_re)/$escape{$1}/g;
79     return $html;
80     }
81    
82     sub html_dump {
83 dpavlin 543 my ( $self, $dump ) = @_;
84     $dump = dump( $dump ) if ref($dump);
85     my $width = $self->html_dump_width;
86     $dump =~ s{(\n[^\n]{$width})([^\n]+?)([^\n]{5})}{\n$1...$3}gs;
87     $dump = $self->html_escape( $dump );
88     $dump =~ s{\Q...\E}{&hellip;}gs;
89 dpavlin 581 # $dump =~ $self->editor_links( $dump ); # FIXME include this
90 dpavlin 543 return "<code>$dump</code>";
91 dpavlin 535 }
92    
93 dpavlin 546 sub popup { my $self = shift; $self->popup_dropdown('popup', @_); }
94     sub dropdown { my $self = shift; $self->popup_dropdown('dropdown', @_); }
95    
96 dpavlin 596 our $re_html = qr{<(?:!--.+?--|(\w+).+?/\1|[^>]+/)>}s; # relaxed html check for one semi-valid tag
97 dpavlin 546
98     sub popup_dropdown {
99     my ( $self, $type, $name, $content, $full ) = @_;
100    
101     $content = $self->html_dump($content) if ref $content;
102    
103     $content = qq|<span>$content</span>| unless $content =~ m{^\s*<(span|a|code).+?/\1>\s*};
104    
105 dpavlin 581 $content =~ s{<span>(<code>[^<]+</code>)</span>}{$1} && $self->TODO("code wrapped in span");
106    
107 dpavlin 591 warn "## $type [$name] = ", length( $content ), " bytes"; # if $self->debug; # FIXME
108 dpavlin 546
109     if ( $name =~ m{::} && $name !~ $re_html ) {
110     return qq|<a class="frey-$type" target="$name" href="/$name">$name $content</a>\n|;
111 dpavlin 588 } elsif ( $name =~ s{^\s*(<a)\s+}{$1 class="frey-$type"} ) {
112     return qq|$name $content\n|;
113 dpavlin 546 } else {
114     return qq|<span class="frey-$type">$name $content</span>\n|;
115     }
116     }
117    
118 dpavlin 161 sub _inline_path {
119     my ( $self, $path ) = @_;
120     -s $path < $self->inline_smaller_than;
121     }
122    
123 dpavlin 156 sub _head_html {
124     my $self = shift;
125 dpavlin 121 my $out = '';
126 dpavlin 577 foreach my $path ( @head ) {
127 dpavlin 121 $path =~ s!^/!!;
128 dpavlin 156 if ( $path =~ m/\.js$/ ) {
129 dpavlin 161 $out .= $self->_inline_path( $path ) ?
130 dpavlin 600 qq|<script type="text/javascript">\n/* inline $path */\n\n| . read_file($path) . qq|\n</script>| :
131 dpavlin 161 qq|<script type="text/javascript" src="/$path"></script>|;
132 dpavlin 156 } elsif ( $path =~ m/\.css$/ ) {
133 dpavlin 161 $out .= $self->_inline_path( $path ) ?
134 dpavlin 600 qq|<style type="text/css">\n/* inline $path */\n\n| . read_file( $path ) . qq|\n</style>| :
135 dpavlin 161 qq|<link type="text/css" rel="stylesheet" href="/$path" media="screen">|;
136 dpavlin 446 } elsif ( $path =~ m{<.+>}s ) {
137 dpavlin 444 $out .= $path;
138 dpavlin 156 } else {
139     confess "don't know how to render $path";
140     }
141 dpavlin 163 $out .= "\n";
142 dpavlin 121 }
143     return $out;
144     }
145 dpavlin 100
146 dpavlin 154 =head2 add_head
147    
148     $o->add_head( 'path/to/external.js' );
149    
150     my $size = $o->add_head( 'path/to/external.css' );
151    
152 dpavlin 445 $o->add_head( '<!-- html content -->' );
153    
154 dpavlin 154 =cut
155    
156     sub add_head {
157     my ( $self, $path ) = @_;
158     return if ! defined $path || $path eq '';
159     $path =~ s!^/!!;
160    
161 dpavlin 546 if ( $path =~ $re_html ) {
162 dpavlin 577 push @head, $path;
163 dpavlin 444 } elsif ( -e $path ) {
164 dpavlin 156 if ( $path =~ m/\.(?:js|css)$/ ) {
165 dpavlin 577 push @head, $path;
166 dpavlin 154 } else {
167     confess "can't add_head( $path ) it's not js or css";
168     }
169 dpavlin 444 return -s $path;
170 dpavlin 154 } else {
171     confess "can't find $path: $!";
172     }
173    
174     }
175    
176 dpavlin 577 sub add_css {
177     my ($self,$css) = @_;
178     my ( $package, $path, $line ) = caller;
179     $self->add_head( qq|
180     <style type="text/css">
181 dpavlin 581 /* via $package at $path line $line */
182 dpavlin 577 $css
183     </style>
184     | );
185     }
186    
187 dpavlin 142 our $reload_counter = 0;
188    
189 dpavlin 183
190     =head2 page
191    
192     $self->page(
193     title => 'page title',
194     head => '<!-- optional head markup -->',
195     body => '<b>Page Body</b>',
196     );
197    
198     =cut
199    
200 dpavlin 519 our @status;
201     sub status { @status };
202    
203 dpavlin 527 our $icon_html;
204    
205 dpavlin 121 sub page {
206 dpavlin 100 my $self = shift;
207 dpavlin 121 my $a = {@_};
208 dpavlin 100
209 dpavlin 519 warn "## page ",dump($a);
210    
211 dpavlin 142 $reload_counter++;
212    
213 dpavlin 388 my $status_line = '';
214 dpavlin 519
215     foreach my $part ( @status ) {
216 dpavlin 388 foreach my $name ( keys %$part ) {
217 dpavlin 537 $status_line .= $self->popup( $name, $part->{$name} );
218 dpavlin 388 }
219     }
220    
221 dpavlin 439 my $url = $self->request_url;
222     $url =~ s{\?reload=\d+}{};
223    
224 dpavlin 460 my $body = $a->{body};
225 dpavlin 591 if ( ! $body ) {
226     my $run = $a->{run} || 'as_markup';
227 dpavlin 611 warn "# no body, invoke $self->$run on ", ref($self);
228 dpavlin 591 $body = $self->$run;
229     }
230 dpavlin 460 if ( $self->content_type !~ m{html} ) {
231     warn "# return only $self body ", $self->content_type;
232     return $body
233     } elsif ( ! defined $body ) {
234     warn "# no body";
235     $body = '<!-- no body -->';
236     }
237 dpavlin 448
238 dpavlin 532 $status_line .= $self->warnings_html;
239 dpavlin 482
240 dpavlin 477 my ($exit,$description) = ('exit','stop server');
241     ($exit,$description) = ('restart','restart server')
242     if $ENV{FREY_RESTART}; # tune labels on exit link
243    
244 dpavlin 473 my $right =
245     qq|
246     <span class="right">
247 dpavlin 519 <a title="reload $url" href="/reload$url">reload</a>
248 dpavlin 644 <a title="$description" href="/exit$url" target="exit">$exit</a>
249 dpavlin 473 </span>
250     |;
251    
252 dpavlin 577 my $svk = Frey::SVK->new;
253     my $info = $svk->info;
254     my $revision = $svk->info->{Revision} || '';
255 dpavlin 516 $revision = $1 if $info->{'Mirrored From'} =~ m{Rev\.\s+(\d+)};
256 dpavlin 505
257 dpavlin 527 $self->add_icon unless $icon_html;
258 dpavlin 524
259 dpavlin 388 my $html = join("\n",
260     qq|<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"><html><head>|,
261     $self->_head_html,
262 dpavlin 418 '<title>' . ( $self->title || $a->{title} || ref($self) ) . '</title>',
263 dpavlin 388 '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">',
264 dpavlin 527 ( $icon_html || '<!-- no icon -->' ),
265 dpavlin 388 ( $a->{head} || '' ),
266     qq|
267 dpavlin 448 </head><body>
268     $body
269 dpavlin 388 <div class="frey-status-line">
270 dpavlin 505 <a href="/">Frey</a> $Frey::VERSION $revision
271 dpavlin 388 $status_line
272 dpavlin 473 $right
273 dpavlin 210 </div>
274     </body></html>
275 dpavlin 388 |,
276     );
277 dpavlin 100
278 dpavlin 121 warn "## >>> page ",length($html), " bytes\n" if $self->debug;
279 dpavlin 100
280 dpavlin 121 return $html;
281 dpavlin 100 }
282    
283 dpavlin 480 =head2 editor
284    
285     Create HTML editor link with optional line and title
286    
287     my $html = $self->editor( $class, $line, $title );
288    
289     =cut
290    
291     sub editor {
292     my ( $self, $class, $line, $title ) = @_;
293     confess "need class" unless $class;
294 dpavlin 519 if ( ! defined $title ) {
295     $title = "edit $class";
296     $title .= " line $line" if $line;
297     }
298     $line ||= 1;
299 dpavlin 480 qq|<a target="editor" href="/editor+$class+$line"| .
300     ( $title ? qq| title="$title"| : '' ) .
301     qq|>$class</a>|;
302     }
303    
304     =head2 editor_links
305    
306     Create HTML links to editor for perl error message
307    
308     my $html = $self->editor_links( $error )
309    
310     =cut
311    
312 dpavlin 468 sub editor_links {
313     my ( $self, $error ) = @_;
314    
315 dpavlin 591 # $error =~ s[(bless\({\s+.+?\s+},\s+)("[^"]+")(\) at)][<span class="frey-dropdown">$1<code>$2</code>$3</span>]gs; # FIXME insert bless hiding back
316    
317 dpavlin 468 $error =~ s{at\s+(\S+)\s+line\s+(\d+)}
318     {at <a target="editor" href="/editor+$1+$2">$1</a> line $2}gsm;
319    
320 dpavlin 581 $error =~ s{(via (?:package) "?)([\w:]+)("?)}
321 dpavlin 468 {$1<a target="editor" href="/editor+$2+1">$2</a>$3}gsm;
322    
323     return $error;
324     }
325    
326 dpavlin 350 sub error {
327 dpavlin 397 my $self = shift;
328     my $error = join(" ", @_);
329 dpavlin 460
330 dpavlin 465 my @backtrace = $self->backtrace;
331     $error .= "\n\t" . join( "\n\t", @backtrace ) if @backtrace;
332 dpavlin 460
333     warn "ERROR: $error\n";
334 dpavlin 468 return
335     qq|<pre class="frey-error">|
336     . $self->editor_links( $error ) .
337     qq|</pre>|
338     ;
339 dpavlin 350 }
340    
341 dpavlin 577 =head1 Status line
342    
343     =head2 add_status
344    
345 dpavlin 581 $self->add_status( { name => { some => 'data' } } );
346 dpavlin 577
347     $self->add_status( "append to last status popup" );
348    
349     =cut
350    
351 dpavlin 507 sub add_status {
352     my ( $self, $data ) = @_;
353 dpavlin 581 push @status, { 'X' => [ $self->backtrace ] };
354 dpavlin 577 if ( ref($data) ) {
355     push @status, $data;
356     } else {
357 dpavlin 581 if ( defined $status[ $#status ] ) {
358     $status[ $#status ]->{ '+' } = $data;
359     } else {
360     push @status, { '+' => $data };
361     }
362 dpavlin 577 }
363 dpavlin 507 }
364    
365 dpavlin 577 =head2 clean_status
366    
367     Called at beginning of each request
368    
369     $self->clean_status;
370    
371     =cut
372    
373 dpavlin 519 sub clean_status {
374 dpavlin 577 my ($self) = shift;
375 dpavlin 578 @head = ( 'static/frey.css' );
376 dpavlin 625 my $params = { request_url => $self->request_url };
377 dpavlin 581 @status = (
378 dpavlin 625 { 'ClassBrowser' => Frey::ClassBrowser->new( %$params, usage_on_top => 0 )->as_markup },
379     { 'Bookmarklets' => Frey::Bookmarklet->new( %$params )->as_markup },
380     { 'INC' => Frey::INC->new( %$params )->as_markup },
381 dpavlin 581 );
382 dpavlin 527 $icon_html = '';
383 dpavlin 519 }
384    
385 dpavlin 577 =head2 status_parts
386    
387     Dump all status line parts
388    
389     $self->status_parts
390    
391     =cut
392    
393 dpavlin 519 sub status_parts {
394     warn "## status parts ", dump( map { keys %$_ } @status );
395     }
396    
397 dpavlin 568 =for debug
398    
399 dpavlin 518 sub DEMOLISH {
400     my ( $self ) = @_;
401 dpavlin 529 warn "## $self DEMOLISH status ", $#status + 1, " elements ", dump( map { keys %$_ } @status ) if @status;
402 dpavlin 518 }
403    
404 dpavlin 568 =cut
405    
406 dpavlin 527 =head2 add_icon
407    
408     Frey::Foo->add_icon; # /static/icons/Frey/Foo.png
409     Frey::Foo->add_icon('warning'); # /static/icons/Frey/Foo/warning.png
410    
411     =cut
412    
413 dpavlin 529 sub icon_path {
414     my ($self,$class,$variant) = @_;
415     my $icon = $class;
416 dpavlin 524 $icon =~ s{::}{/}g;
417 dpavlin 529 $icon .= "/$variant" if $variant;
418     my $path = 'static/icons/' . $icon . '.png';
419     if ( -e $path ) {
420 dpavlin 577 warn "# $class from $self icon_path $path" if $self->debug;
421 dpavlin 529 return $path;
422     } else {
423 dpavlin 564 $self->TODO( "add $path icon for $class" );
424 dpavlin 529 return undef;
425     }
426     }
427 dpavlin 524
428 dpavlin 529 sub add_icon {
429     my ($self,$variant) = @_;
430 dpavlin 524
431 dpavlin 529 my $class = ref($self);
432     $class = $self->class if $self->can('class');
433     my $icon_path = $self->icon_path( $class, $variant ) || return;
434 dpavlin 524
435 dpavlin 529 $icon_html .= qq|<link rel="icon" type="image/png" href="/$icon_path">|;
436     warn "# using icon $icon_path";
437    
438 dpavlin 527 =for later
439    
440 dpavlin 529 # FIXME http://en.wikipedia.org/wiki/Favicon suggest just rel="icon" but that doesn't seem to work!
441     my $ico_path = $icon_path;
442     $ico_path =~ s{png$}{ico};
443     if ( ! -e $ico_path ) {
444     system "convert $icon_path $ico_path";
445     warn "# convert $icon_path $ico_path : $@";
446     }
447     $icon_html .= qq|<link rel="shortcut icon" type="image/x-icon" href="/$ico_path">| if -e $ico_path;
448 dpavlin 524
449 dpavlin 527 =cut
450    
451 dpavlin 524 }
452    
453 dpavlin 532 my $warn_colors = {
454     '#' => '#444',
455     '##' => '#888',
456     };
457    
458     my $multiline_markers = {
459 dpavlin 535 '(' => ')',
460 dpavlin 532 '{' => '}',
461 dpavlin 535 '[' => ']',
462 dpavlin 532 '"' => '"',
463     };
464    
465     my $multiline_re = '[\\' . join('\\', keys %$multiline_markers ) . ']';
466     warn "## multiline markers ", dump( $multiline_markers ), " -> $multiline_re";
467    
468     sub log_path {
469 dpavlin 629 $Frey::Bootstrap::log_path || die "no log_path?";
470 dpavlin 532 }
471    
472     sub warnings_html {
473     my ($self,$level) = shift;
474 dpavlin 535 $level ||= $self->debug,
475 dpavlin 532 my $path = $self->log_path;
476    
477 dpavlin 611 my $max = 30;
478 dpavlin 588 my $pos = 0;
479     my @warnings = ( '' x $max ); # XXX circualar buffer for 50 lines
480 dpavlin 532 my $line = 0;
481     my $multiline_end;
482    
483 dpavlin 611 # XXX do we really want to do this every time?
484     my $css = qq|/* short css classes for levels */\n|;
485     my $level_to_class;
486     foreach ( keys %$warn_colors ) {
487     my $l = length($_);
488     my $class = 'l' . $l;
489     $css .= qq|.$class { color: $warn_colors->{$_} }\n|;
490     $level_to_class->{ $_ } = $class;
491     }
492     $self->add_css( $css );
493    
494 dpavlin 532 open(my $log, '<', $path) || die "can't open $path: $!";
495     while(<$log>) {
496     chomp;
497     $line++;
498    
499 dpavlin 634 next if m{^\s+(Mojo|Class::MOP|Moose)::};
500    
501 dpavlin 532 my $style = '';
502    
503 dpavlin 588 =for filter
504    
505 dpavlin 535 if ( $multiline_end ) {
506     if ( m{^\Q$multiline_end\E} || m{^\s.+\Q$multiline_end\E;$} ) {
507 dpavlin 537 # warn "## $line end of $multiline_end in '$_'\n";
508 dpavlin 535 undef $multiline_end;
509     } else {
510 dpavlin 537 # warn "## $line skipped\n";
511 dpavlin 535 }
512     } elsif ( m{^(#*)\s+} ) {
513 dpavlin 532 my $l = $1 ? length($1) : 0;
514 dpavlin 535 if ( $l > $level ) {
515     undef $multiline_end;
516     $multiline_end = $multiline_markers->{$1} if m{($multiline_re)$};
517 dpavlin 537 # warn "## $line start $1 .. $multiline_end level $l > $level for '$_'\n" if $multiline_end;
518 dpavlin 535 next;
519     }
520 dpavlin 532
521 dpavlin 588 =cut
522 dpavlin 611 if ( m{^(#*)} ) {
523 dpavlin 588
524 dpavlin 611 my $level = $1;
525     my $msg = $_;
526 dpavlin 532
527 dpavlin 535 my $spacer = ' ';
528 dpavlin 611 my $real_msg = expand( $msg );
529     if ( length($real_msg) > $self->html_dump_width ) {
530    
531     $real_msg = substr( $msg, 0, $self->html_dump_width );
532     $msg = unexpand( $real_msg );
533 dpavlin 535 $spacer = '&hellip;'
534     }
535 dpavlin 611
536     $msg = $self->html_escape( $msg );
537    
538     if ( my $class = $level_to_class->{ $level } ) {
539     $msg = qq|<span class="$class">$msg</span>|;
540     }
541    
542 dpavlin 613 #$msg .= $spacer . qq|<a target="editor" href="/editor+$path+$line" style="float: right;">$line</a>\n|;
543     $msg = qq|<a target="editor" href="/editor+$path+$line" style="float: right;">$line</a>$msg|
544     . ( $spacer ? $spacer : '' )
545     . "\n"; # XXX <pre> needs this
546 dpavlin 611
547     $warnings[ $pos++ % $max ] = $msg;
548 dpavlin 532 }
549     }
550 dpavlin 611 warn "log has $line lines tell position ",tell($log);
551 dpavlin 532 close($log) || die "can't close $path: $!";
552    
553 dpavlin 588 my $size = -s $path;
554    
555 dpavlin 611 my $warnings = join("",
556 dpavlin 613 map { $warnings[ ( $pos + $_ ) % $max ] || '' } 0 .. ( $max - 1 )
557 dpavlin 588 );
558    
559     my $s = length($warnings);
560    
561 dpavlin 532 return
562 dpavlin 611 # need to wrap editor link into span so we can have links in warnings
563 dpavlin 588 qq|<span class="frey-popup"><a target="editor" href="/editor+$path+$line" title="$path \| $size -> $s bytes \| $line -> $pos lines \| level $level">warn</a><code>|
564 dpavlin 611 . $self->editor_links( $warnings )
565 dpavlin 588 . qq|</code></span></a>|
566 dpavlin 532 ;
567     }
568    
569 dpavlin 543
570     =head2 backtrace
571    
572     Show backtrace with links to editor
573    
574     my @backtrace = $self->backtrace;
575    
576     =cut
577    
578     sub backtrace {
579     my ($self) = @_;
580    
581     my @backtrace;
582     foreach ( 0 .. 5 ) {
583     my (
584     $package,$path,$line
585     # subroutine hasargs
586     # wantarray evaltext is_require
587     # hints bitmask hinthash
588     ) = caller($_) or last;
589    
590     push @backtrace,
591 dpavlin 581 qq|via $package at $path line $line|;
592 dpavlin 543 }
593 dpavlin 591 #warn "# backtrace: ", dump( @backtrace ) if @backtrace;
594 dpavlin 543 return @backtrace;
595     }
596    
597 dpavlin 100 1;

  ViewVC Help
Powered by ViewVC 1.1.26