File Coverage

blib/lib/CGI/Inspect.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package CGI::Inspect;
2              
3             =head1 NAME
4              
5             CGI::Inspect - Inspect and debug CGI apps with an in-browser UI
6              
7             =head1 SYNOPSIS
8              
9             use CGI::Inspect; # This exports inspect()
10              
11             print "Content-type: text/html\n\n";
12              
13             my $food = "toast";
14              
15             for my $i (1..10) {
16             print "$i cookies for me to eat...
";
17             inspect() if $i == 5; # be sure to edit $toast :)
18             }
19              
20             print "I also like $food!";
21              
22             =head1 DESCRIPTION
23              
24             This class is a drop-in web based inspector for plain CGI (or CGI-based)
25             applications. Include the library, and then call inspect(). In your server
26             error logs you'll see something like "Please connect to localhost:8080". When
27             you do, you'll be greeted with an inspection UI which includes a stack trace,
28             REPL, and other goodies.
29              
30             To work it's magic this modules uses Continuity, Devel::LexAlias,
31             Devel::StackTrace::WithLexicals, and their prerequisites (such as PadWalker).
32             Remember that you can always install such things locally for debugging -- no
33             need to install them systemwide (in case you are afraid of the power that they
34             provide).
35              
36             =cut
37              
38 1     1   29703 use strict;
  1         2  
  1         45  
39 1     1   541 use Continuity;
  0            
  0            
40             use Continuity::RequestCallbacks;
41             use base 'Exporter';
42             our @EXPORT = qw( inspect );
43             our @EXPORT_OK = qw( nodie );
44              
45             our $VERSION = '0.5';
46              
47             =head1 EXPORTED SUBS
48              
49             =head2 inspect()
50              
51             This starts the Continuity server and inspector on the configured port
52             (defaulting to 8080). You can pass it parameters which it will then pass on to
53             CGI::Inspect->new. The most useful one is probably port:
54              
55             inspect( port => 12345 );
56              
57             Another useful parameter is plugins. The default is:
58              
59             plugins => [qw( BasicLook Exit REPL CallStack )]
60              
61             =cut
62              
63             sub inspect {
64             print STDERR "Starting inspector...\n";
65             require IO::Handle;
66             STDERR->autoflush(1);
67             STDOUT->autoflush(1);
68             print "\n";
69             my $self = CGI::Inspect->new(@_);
70             $self->start_inspecting;
71             }
72              
73             # This might be cool, but we'll disable it for now. I mean... because it doesn't work.
74             # sub import {
75             # my $class = shift;
76             # my $nodie = grep { $_ eq 'nodie' } @_;
77             # $SIG{__DIE__} = \&CGI::Inspect::inspect unless $nodie;
78             # $class->SUPER::import(@_);
79             # }
80              
81              
82             =head1 PLUGINS
83              
84             CGI::Inspect comes with a few plugins by default:
85              
86             =over 4
87              
88             =item * L - A simple Read-Eval-Print prompt
89              
90             =item * L - A pretty stack trace (with lexical editing!)
91              
92             =item * L - Stop inspecting
93              
94             =back
95              
96             =head2 Creating Plugins
97              
98             Plugins are easy to create! They are really just subroutines that return a
99             string for what they want printed. All of the built-in plugins actuall inherti
100             from L, which just provides some convenience methods. The
101             main CGI::Inspect module will create an instance of your plugin with
102             Plugin->new, and then will execute it with $plugin->process.
103              
104             Plugins can, however, make use of Continuity, including utilizing callbacks.
105             Here is the complete source to the 'Exit' plugin, as a fairly simple example.
106              
107             package CGI::Inspect::Plugin::Exit;
108              
109             use strict;
110             use base 'CGI::Inspect::Plugin';
111              
112             sub process {
113             my ($self) = @_;
114             my $exit_link = $self->request->callback_link(
115             Exit => sub {
116             $self->manager->{do_exit} = 1;
117             }
118             );
119             my $output = qq{
120            
121             $exit_link
122            
123             };
124             return $output;
125             }
126              
127             1;
128              
129             For now, read the source of the default plugins for further ideas. And of
130             course email the author if you have any questions or ideas!
131              
132             =head1 METHODS
133              
134             These methods are all internal. All you have to do is call inspect().
135              
136             =head2 CGI::Inspect->new()
137              
138             Create a new monitor object.
139              
140             =cut
141              
142             sub new {
143             my ($class, %params) = @_;
144             my $self = {
145             port => 8080,
146             plugins => [qw(
147             BasicLook Exit REPL CallStack
148             )],
149             # REPL CallStack Exit Counter FileEdit
150             plugin_objects => [],
151             html_headers => [],
152             %params
153             };
154             bless $self, $class;
155             return $self;
156             }
157              
158             =head2 $self->start_inspecting
159              
160             Load plugins and start inspecting!
161              
162             =cut
163              
164             sub start_inspecting {
165             my ($self) = @_;
166             $self->load_plugins;
167             $self->start_server;
168             }
169            
170             # use Devel::StackTrace::WithLexicals;
171             # my $trace = Devel::StackTrace::WithLexicals->new(
172             # ignore_package => [qw( Devel::StackTrace CGI::Inspect )]
173             # );
174             # $self->trace( $trace );
175              
176             =head2 $self->start_server
177              
178             Initialize the Continuity server, and begin the run loop.
179              
180             =cut
181              
182             sub start_server {
183             my ($self) = @_;
184             my $docroot = $INC{'CGI/Inspect.pm'};
185             $docroot =~ s/Inspect.pm/Inspect\/htdocs/;
186             #print STDERR "docroot: $docroot\n";
187             my $server = Continuity->new(
188             port => $self->{port},
189             docroot => $docroot,
190             callback => sub { $self->main(@_) },
191             #debug_callback => sub { STDERR->print("@_\n") },
192             );
193             $server->loop;
194             print STDERR "Done inspecting!\n";
195             }
196              
197             =head2 $self->display
198              
199             Display the current page, based on $self->{content}
200              
201             =cut
202              
203             sub display {
204             my ($self, $content) = @_;
205             my $id = $self->request->session_id;
206             my $html_headers = join '', @{ $self->{html_headers} };
207             if($self->request->param('no_html_wrapper')) {
208             $self->request->print($content);
209             } else {
210             $self->request->print(qq|
211            
212            
213             CGI::Inspect
214             $html_headers
215            
216            
217            
218            
219             $content
220            
221            
222            
223             |);
224             }
225             }
226              
227             =head2 $self->request
228              
229             Returns the current request obj
230              
231             =cut
232              
233             sub request {
234             my ($self) = @_;
235             return $self->{request};
236             }
237              
238             =head2 $self->load_plugins
239              
240             Load all of our plugins.
241              
242             =cut
243              
244             sub load_plugins {
245             my ($self) = @_;
246             my $base = "CGI::Inspect::Plugin::";
247             foreach my $plugin (@{ $self->{plugins} }) {
248             my $plugin_pkg = $base . $plugin;
249             eval "use $plugin_pkg";
250             print STDERR "Error loading $plugin_pkg, $@\n" if $@;
251             my $plugin_instance = $plugin_pkg->new( manager => $self );
252             push @{ $self->{plugin_objects} }, $plugin_instance;
253             $self->{plugins_by_name}->{$plugin_pkg} = $plugin_instance;
254             }
255             }
256              
257             =head2 $self->main
258              
259             This is executed as the entrypoint for inspector sessions.
260              
261             =cut
262              
263             sub main {
264             my ($self, $request) = @_;
265             $self->{request} = $request; # For plugins to use
266             $self->{do_exit} = 0;
267             do {
268             my $content = '';
269             if($request->param('plugin')) {
270             $content .= $self->{plugins_by_name}->{$request->param('plugin')}->process();
271             } else {
272             foreach my $plugin (@{$self->{plugin_objects}}) {
273             $content .= $plugin->process();
274             }
275             }
276             $self->display($content);
277             $request->next->execute_callbacks
278             unless $self->{do_exit};
279             } until($self->{do_exit});
280             $request->print("");
281             Coro::Event::unloop();
282             $request->print("Exiting...");
283             $request->end_request;
284             }
285              
286             =head1 SEE ALSO
287              
288             L
289              
290             =head1 AUTHOR
291              
292             Brock Wilcox - http://thelackthereof.org/
293              
294             =head1 COPYRIGHT
295              
296             Copyright (c) 2008-2009 Brock Wilcox . All rights
297             reserved. This program is free software; you can redistribute it and/or
298             modify it under the same terms as Perl itself.
299              
300             =cut
301              
302             1;
303