use strict; use warnings; package Jifty::Plugin::Halo; use base qw/Jifty::Plugin/; use Time::HiRes 'time'; use Class::Trigger; =head1 NAME Jifty::Plugin::Halo - Provides halos =head1 DESCRIPTION This plugin provides L halos for your application. It's included by default when using Jifty with DevelMode turned on. =cut =head2 init Only enable halos in DevelMode. Add our instrumentation to L. =cut sub init { my $self = shift; return if $self->_pre_init; return unless Jifty->config->framework('DevelMode'); warn "Overwriting an existing Template::Declare->around_template" if Template::Declare->around_template; Template::Declare->around_template(sub { $self->around_template(@_) }); } =head2 around_template This is called to instrument L templates. We also draw a halo around the template itself. =cut sub around_template { my ($self, $orig, $path, $args, $code) = @_; # for now, call the last piece of the template's path the name $path =~ m{.*/(.+)}; my $name = $1 || $path; my $frame = $self->push_frame( args => [ %{ Jifty->web->request->arguments } ], path => $path, name => $name, ); $frame->{displays}->{P} = { name => "perl", callback => sub { my $deparsed = eval { require Data::Dump::Streamer; Data::Dump::Streamer::Dump($code)->Out; }; Jifty->web->escape($deparsed); }, }; my $proscribed = $self->is_proscribed($frame); Template::Declare->buffer->append($self->halo_header($frame)) unless $proscribed; $orig->(); $frame = $self->pop_frame; Template::Declare->buffer->append($self->halo_footer($frame)) unless $proscribed; } =head2 halo_header frame -> string This will give you the halo header which includes links to the various information displays for this template. =cut sub halo_header { my $self = shift; my $frame = shift; my $id = $frame->{id}; my $name = Jifty->web->escape($frame->{name}); my $displays = $frame->{displays}; my @buttons; for my $letter (sort keys %$displays) { my $d = $displays->{$letter}; my $name = Jifty->web->escape($d->{name}); push @buttons, join "\n", grep { $_ } qq{{default} && qq{ style="font-weight:bold"}, qq{ href="#">$letter}, } my $rendermode = '[ ' . join(' | ', @buttons) . ' ]'; return << " HEADER";
$rendermode
$name
HEADER } =head2 halo_footer frame -> string This will give you the halo footer which includes the actual information to be displayed when the user pokes the halo. =cut sub halo_footer { my $self = shift; my $frame = shift; my $id = $frame->{id}; my $displays = $frame->{displays}; my @divs; for (sort keys %$displays) { my $d = $displays->{$_}; my $name = Jifty->web->escape($d->{name}); if ($d->{callback}) { my $output = qq{"; push @divs, $output; } } my $divs = join "\n", @divs; return << " FOOTER";
$divs
FOOTER } =head2 new_frame -> hashref Gives you a new frame for storing halo information. =cut sub new_frame { my $self = shift; my $args = { name => "arguments", callback => sub { my $frame = shift; my @out; my @args; while (my ($key, $value) = splice(@{$frame->{args}},0,2)) { push @args, [$key, $value]; } for (sort { $a->[0] cmp $b->[0] } @args) { my ($name, $value) = @$_; my $ref = ref($value); my $out = qq{$name: }; if ($ref) { my $expanded = Jifty->web->serial; my $yaml = Jifty->web->escape(Jifty::YAML::Dump($value)); $out .= qq{$ref}; } elsif (defined $value) { $out .= Jifty->web->escape($value); } else { $out .= "undef"; } push @out, $out; } return undef if @out == 0; return "
    " . join("\n", map { "
  • $_
  • " } @out) . "
"; }, }; return { id => Jifty->web->serial, start_time => time, subcomponent => 0, proscribed => 0, displays => { R => { name => "render", default => 1 }, S => { name => "source" }, A => $args, }, @_, }; } =head2 push_frame -> frame Creates and pushes a frame onto the render stack. Mason's halos do not support "around"ing a component, so we fake it with an explicit stack. This also triggers C for plugins adding halo data. =cut sub push_frame { my $self = shift; my $STACK = Jifty->handler->stash->{'_halo_stack'} ||= []; my $DEPTH = ++Jifty->handler->stash->{'_halo_depth'}; my $INDEX_STACK = Jifty->handler->stash->{'_halo_index_stack'} ||= []; # if this is the first frame, discard anything from the previous queries my $previous = $STACK->[-1] || {}; my $frame = $self->new_frame(@_, previous => $previous, depth => $DEPTH); push @$STACK, $frame; push @$INDEX_STACK, $#$STACK; $self->call_trigger('halo_pre_template', frame => $frame, previous => $previous); return $frame; } =head2 pop_frame -> frame Pops a frame off the render stack. Mason's halos do not support "around"ing a component, so we fake it with an explicit stack. This also triggers C for plugins adding halo data. =cut sub pop_frame { my $self = shift; my $STACK = Jifty->handler->stash->{'_halo_stack'} ||= []; my $INDEX_STACK = Jifty->handler->stash->{'_halo_index_stack'} ||= []; my $FRAME_ID = pop @$INDEX_STACK; my $frame = $STACK->[$FRAME_ID]; my $previous = $frame->{previous}; $frame->{'end_time'} = time; $self->call_trigger('halo_post_template', frame => $frame, previous => $previous); --Jifty->handler->stash->{'_halo_depth'}; return $frame; } =head2 is_proscribed FRAME Returns true if the given C should not have a halo around it. =cut sub is_proscribed { my ($self, $frame) = @_; return 1 if $frame->{'proscribed'}; $frame->{'proscribed'} = 1 unless Jifty->handler->stash->{'in_body'}; return $frame->{'proscribed'}? 1 : 0; } 1;