File Coverage

blib/lib/Padre/Plugin/Plack.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Padre::Plugin::Plack;
2 1     1   25435 use base 'Padre::Plugin';
  1         2  
  1         1364  
3              
4 1     1   8 use warnings;
  1         2  
  1         45  
5 1     1   18 use strict;
  1         7  
  1         63  
6              
7 1     1   298 use Padre::Util ('_T');
  0            
  0            
8             use Padre::Debug;
9              
10             our $VERSION = '0.01';
11              
12             # Declare the Padre interfaces this plugin uses
13             sub padre_interfaces {
14             'Padre::Plugin' => 0.29;
15             }
16              
17             # Declare ourselves as the handler for .psgi files
18             sub registered_documents {
19             'application/x-psgi' => 'Padre::Document::PSGI';
20             }
21              
22             # Static cache for the dot-psgi examples (read off disk)
23             my %PSGI_EXAMPLES;
24              
25             # Create the plugin menu
26             sub menu_plugins {
27             my $self = shift;
28             my $main = shift;
29              
30             my $menu = Wx::Menu->new;
31              
32             my $app_menu = Wx::Menu->new;
33             $menu->Append( -1, _T('New PSGI App'), $app_menu );
34              
35             for my $basename ( sort keys %PSGI_EXAMPLES ) {
36             Wx::Event::EVT_MENU(
37             $main,
38             $app_menu->Append( -1, _T($basename) ),
39             sub {
40             $self->on_app_load( $PSGI_EXAMPLES{$basename} );
41             return;
42             },
43             );
44             }
45              
46             my $docs_menu = Wx::Menu->new;
47             $menu->Append( -1, _T('Online References'), $docs_menu );
48              
49             Wx::Event::EVT_MENU(
50             $main,
51             $docs_menu->Append( -1, _T('plackperl.org') ),
52             sub {
53             Padre::Wx::launch_browser('http://plackperl.org');
54             }
55             );
56              
57             Wx::Event::EVT_MENU(
58             $main,
59             $docs_menu->Append( -1, _T('Plack Advent Calendar') ),
60             sub {
61             Padre::Wx::launch_browser('http://advent.plackperl.org');
62             },
63             );
64              
65             Wx::Event::EVT_MENU(
66             $main,
67             $menu->Append( -1, _T('About') ),
68             sub { $self->on_about_load },
69             );
70              
71             # Return it and the label for our plug-in
72             return ( $self->plugin_name => $menu );
73             }
74              
75             sub on_app_load {
76             my $self = shift;
77             my $file = shift;
78              
79             my $main = $self->main;
80              
81             # Slurp in the new app content from the template file
82             my $template = Padre::Util::slurp($file);
83             unless ($template) {
84              
85             # Rare failure, no need to translate
86             $self->main->error("Failed to find template '$file'");
87             return;
88             }
89              
90             # Create new document editor tab
91             $main->new_document_from_string( $$template, 'application/x-psgi' );
92             my $editor = $main->current->editor;
93             my $doc = $editor->{Document};
94              
95             # N.B. It used to be necessary to deliberately use application/x-perl mime type and then rebless as
96             # a hack to make syntax highlighting work off the bat, but it seems to work now
97             # $doc->set_mimetype('application/x-psgi');
98             # $doc->rebless;
99             $self->on_doc_load($doc);
100              
101             # The tab exists, so trigger set_tab_icon
102             $doc->set_tab_icon;
103             }
104              
105             sub is_psgi_doc {
106             my $self = shift;
107             my $doc = shift;
108            
109             return $doc->isa('Padre::Document::PSGI') && $doc->can('get_mimetype') && $doc->get_mimetype eq 'application/x-psgi';
110             }
111              
112             sub editor_enable {
113             my $self = shift;
114             my $editor = shift;
115             my $doc = shift;
116            
117             # Only respond to event on psgi docs
118             return unless $self->is_psgi_doc($doc);
119            
120             TRACE('->editor_enable') if DEBUG;
121            
122             $self->on_doc_load($doc);
123              
124             # Deliberately don't trigger Padre::Document::PSGI::set_tab_icon here because the tab doesn't exist yet
125             # (it gets triggered by our tomfoolery in Padre::Document::PSGI::restore_cursor_position)
126             }
127              
128             sub editor_changed {
129             my $self = shift;
130            
131             my $main = $self->main or return;
132             my $editor = $main->current->editor or return;
133             my $doc = $editor->{Document} or return;
134            
135             # Only respond to event on psgi docs
136             return unless $self->is_psgi_doc($doc);
137            
138             TRACE('->editor_changed') if DEBUG;
139            
140             ## TODO: add check that doc is now selected (for safety)..
141             $self->on_panel_load($doc);
142             }
143              
144             sub on_panel_load {
145             my $self = shift;
146             my $doc = shift;
147            
148             if (!$doc->panel) {
149             TRACE('->on_panel_load creating panel') if DEBUG;
150             require Padre::Plugin::Plack::Panel;
151             $doc->panel( Padre::Plugin::Plack::Panel->new($doc) );
152             }
153            
154             # Show the panel, and pass an onclose callback
155             Padre::Current->main->bottom->show( $doc->panel, sub {
156             $self->main->error(q{Sorry Dave, I can't do that});
157            
158             # We can't actually cancel the close, so re-create it
159             $self->plackdown($doc);
160             $doc->panel(undef);
161             $self->on_panel_load($doc);
162             });
163             }
164              
165             sub on_panel_close {
166             my $self = shift;
167             my $doc = shift;
168            
169             return unless $doc && $doc->panel;
170            
171             if (my $panel = $doc->panel) {
172             $self->plackdown($doc);
173             $self->main->bottom->hide( $panel );
174             $doc->panel(undef);
175             }
176             }
177              
178             # Note that the new tab may or may not exist at this point
179             # When triggered by user opening a new file (e.g. from L), tab does not exist yet
180             # Whereas, when triggered by user creating new app from template, tab exists
181             sub on_doc_load {
182             my $self = shift;
183             my $doc = shift;
184            
185             TRACE('->on_doc_load') if DEBUG;
186              
187             if ( !$doc->isa('Padre::Document::PSGI') ) {
188             $self->error('Not a Padre::Document::PSGI, something is broken with Padre::Plugin::Plack');
189             return;
190             }
191              
192             # Set the icon path, but don't actually trigger set_icon_tab() just yet
193             $doc->icon_path( $self->plugin_directory_share . "/icons/16x16/logo.png" );
194             $doc->plugin($self);
195              
196             # Trigger the Document's general setup event
197             $doc->on_load;
198            
199             # Show the panel
200             $self->on_panel_load($doc);
201             }
202              
203             sub on_doc_close {
204             my $self = shift;
205             my $doc = shift;
206            
207             TRACE('->on_doc_close') if DEBUG;
208            
209             if ( !$doc->isa('Padre::Document::PSGI') ) {
210             $self->error('Not a Padre::Document::PSGI, something is broken with Padre::Plugin::Plack');
211             return;
212             }
213            
214             $self->on_panel_close($doc);
215             }
216              
217             sub on_about_load {
218             require Plack;
219             require Class::Unload;
220             my $about = Wx::AboutDialogInfo->new;
221             $about->SetName("Padre::Plugin::Plack");
222             $about->SetDescription(
223             _T('PSGI/Plack support for Padre') . "\n"
224             . _T('by') . "\n"
225             . 'Patrick Donelan (pat@patspam.com)' . "\n\n"
226             . _T('This system is running Plack version')
227             . " $Plack::VERSION\n"
228             . 'http://plackperl.org'
229             );
230             $about->SetVersion($VERSION);
231             Class::Unload->unload('Plack');
232              
233             Wx::AboutBox($about);
234             return;
235             }
236              
237             sub load_dot_psgi_examples {
238             my $self = shift;
239              
240             require File::Find::Rule;
241             %PSGI_EXAMPLES =
242             map { File::Basename::basename($_) => $_ }
243             File::Find::Rule->file()->name('*.psgi')
244             ->in( $self->plugin_directory_share . '/dot-psgi' );
245             }
246              
247             sub plugin_enable {
248             my $self = shift;
249              
250             $self->load_dot_psgi_examples;
251             }
252              
253             sub plugin_disable {
254             my $self = shift;
255              
256             # TODO: Loop over all docs and turn off their psgi goodies: panel, stop server, etc..
257              
258             # cleanup loaded classes
259             require Class::Unload;
260             Class::Unload->unload('Padre::Document::PSGI');
261             Class::Unload->unload('Padre::Plugin::Plack::Panel');
262             }
263              
264             sub plackup {
265             my $self = shift;
266             my $doc = shift;
267            
268             return unless $doc;
269             TRACE('->plackup') if DEBUG;
270            
271             my $main = $self->main;
272             my $filename = $doc->filename;
273            
274             if (!$filename) {
275             $main->on_save;
276             $filename = $doc->filename;
277             return unless $filename;
278             }
279              
280             my $pwd = Cwd::cwd();
281             chdir $doc->dirname;
282            
283             # Server ("Let plackup guess" means leave as unspecified)
284             my $server = $doc->panel->{server}->GetValue;
285             $server = $server eq _T('Let plackup guess') ? '' : "-s $server";
286            
287             # Port (required for browser url)
288             my $port = $doc->panel->{port}->GetValue || 5000;
289             $port = "-p $port";
290            
291             my $restart = $doc->panel->{restart}->GetValue ? '-r' : '';
292             my $plackup_options = $doc->panel->{plackup_options}->GetValue;
293            
294             require File::Which;
295             my $plackup = File::Which::which('plackup');
296             if (!$plackup) {
297             $main->error('Command "plackup" not found in $PATH, are you sure you have Plack installed correctly?');
298             return;
299             }
300            
301             my $cmd = qq{$plackup $port $restart $server $plackup_options "$filename"};
302             TRACE("->plackup $cmd") if DEBUG;
303             $self->run_command($doc, $cmd);
304              
305             # restore previous dir
306             chdir $pwd;
307             }
308              
309             sub plackdown {
310             my $self = shift;
311             my $doc = shift;
312            
313             return unless $doc;
314            
315             TRACE('->plackdown') if DEBUG;
316            
317             my $process = $doc->process;
318             return unless $process;
319            
320             # sanity check
321             if (!$process->IsAlive) {
322             TRACE('->plackdown process was dead but not undef, strange') if DEBUG;
323             $doc->process(undef);
324             }
325            
326             my $processid = $process->GetProcessId();
327             my $panel = $doc->panel;
328            
329             require Proc::Killfam;
330             my @signals = qw(INT TERM QUIT KILL STOP);
331             for my $sig (@signals) {
332             TRACE( "Sending $sig to PID: $processid" ) if DEBUG;
333             my $signalled = Proc::Killfam::killfam($sig, $processid);
334            
335             if ($panel->{restart}->GetValue) {
336             # with auto-restart, we expect 3 processes
337             return if $signalled > 1;
338             } else {
339             # otherwise, just one
340             return if $signalled > 0;
341             }
342             }
343            
344             $panel->output->AppendText("\n" . "Process PID $processid did not respond, you may need to kill it manually\n" );
345             }
346              
347             sub run_command {
348             my ( $self, $doc, $command ) = (@_);
349            
350             my $panel = $doc->panel;
351              
352             # clear the panel
353             $panel->output->Remove( 0, $panel->output->GetLastPosition );
354              
355             # If this is the first time a command has been run, set up the ProcessStream bindings.
356             unless ($panel->{bound}) {
357             TRACE(' setting up ProcessStream bindings') if DEBUG;
358            
359             require Wx::Perl::ProcessStream;
360             if ( $Wx::Perl::ProcessStream::VERSION < .20 ) {
361             $self->main->error(
362             sprintf(
363             _T(
364             'Wx::Perl::ProcessStream is version %s'
365             . ' which is known to cause problems. Get at least 0.20 by typing'
366             . "\ncpan Wx::Perl::ProcessStream"
367             ),
368             $Wx::Perl::ProcessStream::VERSION
369             )
370             );
371             return 1;
372             }
373              
374             Wx::Perl::ProcessStream::EVT_WXP_PROCESS_STREAM_STDOUT(
375             $panel->output,
376             sub {
377             $_[1]->Skip(1);
378             my $outpanel = $_[0];
379             $outpanel->style_good;
380             $outpanel->AppendText( $_[1]->GetLine . "\n" );
381             return;
382             },
383             );
384             Wx::Perl::ProcessStream::EVT_WXP_PROCESS_STREAM_STDERR(
385             $panel->output,
386             sub {
387             $_[1]->Skip(1);
388             my $outpanel = $_[0];
389             $outpanel->style_neutral;
390             $outpanel->AppendText( $_[1]->GetLine . "\n" );
391              
392             return;
393             },
394             );
395             Wx::Perl::ProcessStream::EVT_WXP_PROCESS_STREAM_EXIT(
396             $panel->output,
397             sub {
398             $_[1]->Skip(1);
399             $_[1]->GetProcess->Destroy;
400            
401             TRACE(' PROCESS_STREAM_EXIT') if DEBUG;
402            
403             my $outpanel = $_[0];
404             $outpanel->style_neutral;
405             $outpanel->AppendText( "\nProcess terminated\n");
406             $panel->set_as_stopped;
407            
408             $doc->process( undef );
409             },
410             );
411             $panel->{bound} = 1;
412             }
413              
414             # Start the command
415             my $process = Wx::Perl::ProcessStream::Process->new( $command, "Run $command", $panel->output );
416             $doc->process( $process->Run );
417              
418             # Check if we started the process or not
419             if ( $doc->process ) {
420             $panel->set_as_started;
421            
422             } else {
423              
424             # Failed to start the command. Clean up.
425             $panel->set_as_stopped; # should already be stopped, but just in case
426             Wx::MessageBox( sprintf( _T("Failed to start server via '%s'"), $command ), _T("Error"), Wx::wxOK, $self );
427             }
428              
429             return;
430             }
431              
432             # This method belonds in Padre::Plugin::Plack::Panel but we keep it here
433             # to speed up the dev edit-reload cycle
434             sub build_panel {
435             my $self = shift;
436             my $doc = shift;
437             my $panel = shift;
438            
439             require Scalar::Util;
440             $panel->{doc} = $doc;
441             Scalar::Util::weaken( $panel->{doc} );
442              
443             # main container
444             my $box = Wx::BoxSizer->new(Wx::wxVERTICAL);
445              
446             # top box, holding buttons, icons and checkboxes
447             my $top_box = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
448              
449             # LED showing process status
450             $panel->{led} = Wx::StaticBitmap->new( $panel, -1, Wx::wxNullBitmap );
451             $top_box->Add( $panel->{led}, 0, Wx::wxALIGN_CENTER_VERTICAL );
452            
453             # Servers
454             my @servers = sort qw(
455             Standalone
456             Standalone::Prefork
457             Standalone::Prefork::Server::Starter
458             FCGI
459             FCGI::EV
460             CGI
461             AnyEvent
462             Coro
463             POE
464             Danga::Socket
465             Server::Simple
466             ReverseHTTP
467             Apache1
468             Apache2
469             );
470             unshift @servers, _T('Let plackup guess');
471             $top_box->AddSpacer(5);
472             $top_box->Add( Wx::StaticText->new( $panel, -1, _T('Server:') ), 0, Wx::wxALIGN_CENTER_VERTICAL );
473             $panel->{server} = Wx::ComboBox->new( $panel, -1, 'Standalone', Wx::wxDefaultPosition, Wx::wxDefaultSize, [ @servers ], Wx::wxCB_DROPDOWN );
474             $top_box->Add( $panel->{server}, 0, Wx::wxALIGN_CENTER_VERTICAL );
475            
476             # Port
477             $top_box->AddSpacer(5);
478             $top_box->Add( Wx::StaticText->new( $panel, -1, _T('Port:') ), 0, Wx::wxALIGN_CENTER_VERTICAL );
479             $panel->{port} = Wx::TextCtrl->new( $panel, -1, '5000' );
480             $top_box->Add( $panel->{port}, 0, Wx::wxALIGN_CENTER_VERTICAL );
481            
482             # Plackup Options
483             $top_box->AddSpacer(5);
484             $top_box->Add( Wx::StaticText->new( $panel, -1, _T('Options:') ), 0, Wx::wxALIGN_CENTER_VERTICAL );
485             $panel->{plackup_options} = Wx::TextCtrl->new( $panel, -1, '' );
486             $top_box->Add( $panel->{plackup_options}, 0, Wx::wxALIGN_CENTER_VERTICAL );
487            
488             # Restart
489             $top_box->AddSpacer(5);
490             $panel->{restart} = Wx::CheckBox->new( $panel, -1, _T('Auto-Restart') );
491             $panel->{restart}->SetValue(1);
492             $top_box->Add( $panel->{restart}, 0, Wx::wxALIGN_CENTER_VERTICAL );
493              
494             # Start/stop button
495             $top_box->AddSpacer(5);
496             $panel->{start_stop} = Wx::Button->new( $panel, -1, '' );
497             Wx::Event::EVT_BUTTON(
498             $panel, $panel->{start_stop},
499             sub {
500             my $panel = shift;
501            
502             # Trigger plackup/down
503             if ( $panel->{start_stop}->GetLabel eq _T('Start') ) {
504             $doc->plugin->plackup($doc);
505             }
506             else {
507             $doc->plugin->plackdown($doc);
508             }
509             },
510             );
511             $top_box->Add( $panel->{start_stop}, 0, Wx::wxALIGN_CENTER_VERTICAL );
512            
513             # Browser
514             $top_box->AddSpacer(5);
515             $panel->{browse} = Wx::Button->new( $panel, -1, _T('View in Browser') );
516             Wx::Event::EVT_BUTTON(
517             $panel, $panel->{browse},
518             sub {
519             my $panel = shift;
520             my $port = $panel->{port}->GetValue || 5000;
521             Padre::Wx::launch_browser("http://0:$port");
522             },
523             );
524             $top_box->Add( $panel->{browse}, 0, Wx::wxALIGN_CENTER_VERTICAL );
525              
526             # finishing up the top_box
527             $box->Add( $top_box, 0, Wx::wxALIGN_LEFT | Wx::wxALIGN_CENTER_VERTICAL );
528              
529             # output panel for server
530             require Padre::Wx::Output;
531             my $output = Padre::Wx::Output->new($panel);
532             $box->Add( $output, 1, Wx::wxGROW );
533              
534             # wrapping it up
535             $panel->SetSizer($box);
536              
537             # holding on to some objects we'll need to manipulate later on
538             $panel->{output} = $output;
539              
540             $panel->set_as_stopped;
541             }
542              
543             1;
544             __END__