Changeset 2226
- Timestamp:
- 12/28/08 15:59:41 (3 years ago)
- Location:
- trunk/Padre/lib/Padre
- Files:
-
- 1 added
- 1 edited
-
PluginHandle.pm (added)
-
PluginManager.pm (modified) (21 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Padre/lib/Padre/PluginManager.pm
r2184 r2226 1 1 package Padre::PluginManager; 2 3 # API NOTES: 4 # This class uses english-style verb_noun method naming 2 5 3 6 =pod … … 18 21 use strict; 19 22 use warnings; 20 use Carp qw (croak);23 use Carp qw{croak}; 21 24 use File::Path (); 22 25 use File::Spec (); 23 use Params::Util qw{_INSTANCE}; 26 use Scalar::Util (); 27 use Params::Util qw{_IDENTIFIER _CLASS _INSTANCE}; 24 28 use Padre::Util (); 29 use Padre::PluginHandle (); 25 30 use Padre::Wx (); 26 31 use Padre::Wx::Menu::Plugins (); … … 57 62 58 63 my $self = bless { 59 parent => $parent, 60 plugins => {}, 61 plugin_dir => Padre::Config->default_plugin_dir, 62 par_loaded => 0, 64 parent => $parent, 65 plugins => {}, 66 plugin_names => [], 67 plugin_dir => Padre::Config->default_plugin_dir, 68 par_loaded => 0, 63 69 @_, 64 70 }, $class; … … 81 87 =head2 plugins 82 88 83 Returns a hash (reference) of plugin names associated with a plugin manager 84 internal structure describing the state of the plugin in the current 85 editor. The contents are somewhat in flux and considered mostly B<PRIVATE>, 86 but the following will likely stay: 87 88 =over 2 89 90 =item module 91 92 Full name of the module that implements the plugin, i.e. C<Padre::Plugin::Foo>. 93 94 =item status 95 96 The status of the plugin. C<failed> indicates failure while trying to load 97 the module. C<new> indicates it was detected as a new plugin. 98 C<loaded> indicates that the module has been successfully loaded, and 99 C<disabled> indicates that it isn't being used as it's been disabled 100 in the configuration. 101 102 Note that this concerns the status of the module in memory. Whether or 103 not to load the plugin is kept in the B<configuration> instead to make 104 it persistent. To check whether a given plugin is enabled, do this: 105 106 if ( Padre->ide->config->{plugins}->{$name}->{enabled} ) {...} 107 108 =item object 109 110 The actual C<Padre::Plugin::Foo> object. Availability depends on the C<status>, 111 of course. The other keys are kept separate since the plugin object is the 112 sole domain of the plugin writer. We don't want them to wreak havoc on 113 our meta data, now do we? 114 115 =back 89 Returns a hash (reference) of plugin names associated with a 90 L<Padre::PluginHandle>. 116 91 117 92 This hash is only populated after C<load_plugins()> was called. … … 126 101 }; 127 102 103 # Get the prefered plugin order. 104 # The order calculation cost is higher than we might like, 105 # so cache the result. 106 sub plugin_names { 107 my $self = shift; 108 unless ( $self->{plugin_names} ) { 109 $self->{plugin_names} = [ 110 sort { 111 ($b->name eq 'My') <=> ($a->name eq 'My') 112 or 113 $a->name cmp $b->name 114 } 115 values %{ $self->{plugins} } 116 ]; 117 } 118 return @{ $self->{plugin_names} }; 119 } 120 121 sub plugin_objects { 122 grep { $_[0]->{plugins}->{$_} } $_[0]->plugin_names; 123 } 124 128 125 129 126 … … 131 128 132 129 ##################################################################### 133 # Configuration and Startup 134 135 =pod 136 137 =head2 plugin_config 138 139 Given a plugin name or namespace, returns a hash reference 140 which corresponds to the configuration section in the Padre 141 YAML configuration of that plugin. Any modifications of that 142 hash reference will, on normal exit, be written to the 143 configuration file. 144 145 If the plugin name is omitted and this method is called from 146 a plugin namespace, the plugin name is determine automatically. 147 148 =cut 149 150 sub plugin_config { 151 my $self = shift; 152 my $plugin = shift; 153 154 # infer the plugin name from caller 155 unless ( defined $plugin ) { 156 my ($package) = caller(); 157 croak("Cannot infer the name of the plugin for which the configuration has been requested") 158 if $package !~ /^Padre::Plugin::/; 159 $plugin = $package; 160 } 161 162 $plugin =~ s/^Padre::Plugin:://; 163 my $config = $self->parent->config; 164 my $plugins = $config->{plugins}; 165 $plugins->{$plugin} ||= {}; 166 return $plugins->{$plugin}; 130 # Bulk Plugin Operations 131 132 =pod 133 134 =head2 reload_plugins 135 136 For all registered plugins, unload them if they were loaded 137 and then reload them. 138 139 =cut 140 141 sub reload_plugins { 142 my $self = shift; 143 my $plugins = $self->plugins; 144 foreach my $name ( sort keys %$plugins ) { 145 # do not use the reload_plugin method since that 146 # refreshes the menu every time. 147 $self->_unload_plugin($name); 148 $self->_load_plugin($name); 149 $self->enable_editors($name); 150 } 151 $self->_refresh_plugin_menu; 152 return 1; 167 153 } 168 154 … … 208 194 # Try the plugin directory first: 209 195 my $plugin_dir = $self->plugin_dir; 210 unshift @INC, $plugin_dir unless grep { $_ eq $plugin_dir } @INC; 211 212 my @dirs = grep {-d $_} map {File::Spec->catdir($_, 'Padre', 'Plugin')} @INC; 196 unless ( grep { $_ eq $plugin_dir } @INC ) { 197 unshift @INC, $plugin_dir; 198 } 199 200 my @dirs = grep { -d $_ } map { File::Spec->catdir($_, 'Padre', 'Plugin') } @INC; 213 201 214 202 require File::Find::Rule; 215 203 my @files = File::Find::Rule->file->name('*.pm')->maxdepth(1)->in( @dirs ); 216 foreach my $file ( @files) {204 foreach my $file ( @files ) { 217 205 # Full path filenames 218 206 my $module = $file; … … 229 217 } 230 218 231 # caller must refresh plugin menu232 $self->_load_plugin _norefresh_menu($module);219 # Caller must refresh plugin menu 220 $self->_load_plugin($module); 233 221 } 234 222 235 223 return; 236 224 } 225 226 =pod 227 228 =head2 alert_new 229 230 The C<alert_new> method is called by the main window post-init and 231 checks for new plugins. If any are found, it presents a message to 232 the user. 233 234 =cut 235 236 sub alert_new { 237 my $self = shift; 238 my $plugins = $self->plugins; 239 my @loaded = sort 240 map { $_->plugin_name } 241 grep { $_->loaded } 242 values %$plugins; 243 if ( @loaded and not $ENV{HARNESS_ACTIVE} ) { 244 my $msg = Wx::gettext(<<"END_MSG") . join("\n", @loaded); 245 We found several new plugins. 246 In order to configure and enable them go to 247 Plugins -> Plugin Manager 248 249 List of new plugins: 250 251 END_MSG 252 253 $self->parent->wx->main_window->message( $msg, 254 Wx::gettext('New plugins detected') 255 ); 256 } 257 258 return 1; 259 } 260 261 =pod 262 263 =head2 failed 264 265 Returns the plugin names (without C<Padre::Plugin::> prefixed) of all plugins 266 that the editor attempted to load but failed. Note that after a failed 267 attempt, the plugin is usually disabled in the configuration and not loaded 268 again when the editor is restarted. 269 270 =cut 271 272 sub failed { 273 my $self = shift; 274 my $plugins = $self->plugins; 275 return grep { 276 $plugins->{$_}->{status} eq 'failed' 277 } keys %$plugins; 278 } 279 280 281 282 283 284 ###################################################################### 285 # PAR Integration 237 286 238 287 # Attempt to load all plugins that sit as .par files in the … … 252 301 $file =~ s/-/::/g; 253 302 254 # caller must refresh plugin menu255 $self->_load_plugin _norefresh_menu($file);303 # Caller must refresh plugin menu 304 $self->_load_plugin($file); 256 305 } 257 306 } … … 266 315 return if $self->{par_loaded}; 267 316 317 # Setup the PAR environment: 268 318 require PAR; 269 # setup the PAR environment:270 319 my $plugin_dir = $self->plugin_dir; 271 320 my $cache_dir = File::Spec->catdir($plugin_dir, 'cache'); … … 275 324 276 325 $self->{par_loaded} = 1; 277 return(); 278 } 326 } 327 328 329 330 331 ###################################################################### 332 # Loading and Unloading a Plugin 279 333 280 334 =pod … … 290 344 sub load_plugin { 291 345 my $self = shift; 292 my $ret = $self->_load_plugin _norefresh_menu(@_);346 my $ret = $self->_load_plugin(@_); 293 347 $self->_refresh_plugin_menu; 294 348 return $ret; 295 349 } 296 350 297 # The guts of load_plugin which don't refresh the menu 298 sub _load_plugin_norefresh_menu { 299 my $self = shift; 300 my $name = shift; 301 my $config = $self->parent->config; 302 my $plugins = $self->plugins; 303 304 # Skip if that plugin was already loaded 351 # This method implements the actual mechanics of loading a plugin, 352 # without regard to the context it is being called from. 353 # So this method doesn't do stuff like refresh the plugin menu. 354 # 355 # MAINTAINER NOTE: This method looks fairly long, but it's doing 356 # a very specific and controlled series of steps. Splitting this up 357 # would just make the process hardner to understand, so please don't. 358 sub _load_plugin { 359 my $self = shift; 360 my $name = shift; 361 362 # If this plugin is already loaded, shortcut and skip 305 363 $name =~ s/^Padre::Plugin:://; 306 if ( $plugins->{$name} and $plugins->{$name}->{status} eq 'enabled' ) { 307 return; 308 } 309 310 $plugins->{$name} ||= {}; 311 my $state = $plugins->{$name}; 312 my $module = $state->{module} = "Padre::Plugin::$name"; 364 if ( $self->plugins->{$name} ) { 365 return; 366 } 367 368 # Create the plugin object (and flush the old sort order) 369 my $module = "Padre::Plugin::$name"; 370 my $plugin = $self->{plugins}->{$name} = Padre::PluginHandle->new( 371 name => $name, 372 class => $module, 373 ); 374 delete $self->{plugin_names}; 313 375 314 376 # Does the plugin load without error 315 eval "use $module"; ## no critic 377 my $code = "use $module ();"; 378 eval $code; ## no critic 316 379 if ( $@ ) { 317 380 $self->{errstr} = sprintf( … … 322 385 $@, 323 386 ); 324 $ state->{status} = 'failed';387 $plugin->status('failed'); 325 388 return; 326 389 } … … 334 397 ), $name, 335 398 ); 336 $ state->{status} = 'failed';399 $plugin->status('failed'); 337 400 return; 338 401 } … … 346 409 ), $name, 347 410 ); 348 $ state->{status} = 'failed';411 $plugin->status('failed'); 349 412 return; 350 413 } … … 359 422 $name, 360 423 ); 361 $ state->{status} = 'failed';424 $plugin->status('failed'); 362 425 return; 363 426 } … … 368 431 # TODO report error in a nicer way 369 432 $self->{errstr} = $@; 370 $ state->{status} = 'failed';433 $plugin->status('failed'); 371 434 return; 372 435 } … … 379 442 $name, 380 443 ); 381 $ state->{status} = 'failed';382 return; 383 } 384 $s tate->{object} = $object;444 $plugin->status('failed'); 445 return; 446 } 447 $self->{object} = $object; 385 448 386 449 # Should we try to enable the plugin 387 unless ( $config->{plugins}->{$name} ) { 450 my $config = $self->plugin_config($plugin); 451 unless ( defined $config->{enabled} ) { 388 452 # Do not enable by default 389 $config->{ plugins}->{$name}->{enabled} = 0;390 $ state->{status} = 'new';391 return; 392 } 393 unless ( $config->{ plugins}->{$name}->{enabled} ) {394 $ state->{status} = 'disabled';453 $config->{enabled} = 0; 454 $plugin->status('unloaded'); 455 return; 456 } 457 unless ( $config->{enabled} ) { 458 $plugin->status('disabled'); 395 459 return; 396 460 } 397 461 398 462 # FINALLY we are clear to enable the plugin 399 $self->_plugin_enable($name); 400 401 return 1; 402 } 403 404 # Assume the named plugin exists, enable it 405 sub _plugin_enable { 406 my $self = shift; 407 my $name = shift; 408 my $plugin = $self->plugins->{$name}->{object}; 409 410 # Call the plugin's own enable method 411 $plugin->plugin_enable; 412 413 # If the plugin defines document types, register them 414 my @documents = $plugin->registered_documents; 415 if ( @documents ) { 416 Class::Autouse->load('Padre::Document'); 417 } 418 while ( @documents ) { 419 my $type = shift @documents; 420 my $class = shift @documents; 421 $Padre::Document::MIME_CLASS{$type} = $class; 422 } 423 424 # Update the status 425 $self->plugins->{$name}->{status} = 'enabled'; 426 427 return 1; 428 } 429 430 # Assume the named plugin exists, disable it 431 sub _plugin_disable { 432 my $self = shift; 433 my $name = shift; 434 my $plugin = $self->plugins->{$name}->{object}; 435 436 # If the plugin defines document types, deregister them 437 my @documents = $plugin->registered_documents; 438 while ( @documents ) { 439 my $type = shift @documents; 440 my $class = shift @documents; 441 delete $Padre::Document::MIME_CLASS{$type}; 442 } 443 444 # Call the plugin's own disable method 445 $plugin->plugin_disable; 446 447 # Update the status 448 $self->plugins->{$name}->{status} = 'disabled'; 463 $plugin->enable; 449 464 450 465 return 1; … … 470 485 # the guts of unload_plugin which don't refresh the menu 471 486 sub _unload_plugin { 487 my $self = shift; 488 my $handle = $self->_plugin(shift); 489 490 # Remember if we are enabled or not 491 $self->plugin_config($handle)->{enabled} = $handle->enabled ? 1 : 0; 492 493 # Disable if needed 494 if ( $handle->enabled ) { 495 $handle->disable; 496 } 497 498 # Destruct the plugin 499 if ( defined $handle->{object} ) { 500 $handle->{object} = undef; 501 } 502 503 # Unload the plugin class itself 504 require Class::Unload; 505 Class::Unload->unload($handle->module); 506 507 # Finally, remove the handle (and flush the sort order) 508 delete $self->{plugins}->{$handle->name}; 509 delete $self->{plugin_names}; 510 511 return 1; 512 } 513 514 =pod 515 516 =head2 reload_plugin 517 518 Reload a single plugin whose name (without C<Padre::Plugin::>) 519 is passed in as first argument. 520 521 =cut 522 523 sub reload_plugin { 472 524 my $self = shift; 473 525 my $name = shift; 474 475 # normalize to plugin name only 476 $name =~ s/^Padre::Plugin:://; 477 my $config = $self->parent->config; 478 my $plugins = $self->plugins; 479 480 return if not defined $plugins->{$name}->{object}; 481 482 eval { 483 $plugins->{$name}->{object}->plugin_disable; 484 }; 485 if ($@) { 486 warn $self->{errstr} = $@; 487 $plugins->{$name}->{status} = 'failed'; 488 # automatically disable the plugin 489 $config->{plugins}->{$name}->{enabled} = 0; # persistent! 490 } else { 491 $plugins->{$name}->{status} = 'disabled'; 492 } 493 494 495 delete $plugins->{$name}; 496 497 require Class::Unload; 498 Class::Unload->unload("Padre::Plugin::$name"); 499 526 $self->_unload_plugin($name); 527 $self->load_plugin($name) or return; 528 $self->enable_editors($name) or return; 500 529 return 1; 501 530 } 502 531 503 =pod 504 505 =head2 reload_plugins 506 507 For all registered plugins, unload them if they were loaded 508 and then reload them. 509 510 =cut 511 512 sub reload_plugins { 513 my $self = shift; 514 my $plugins = $self->plugins; 515 516 foreach my $name (sort keys %$plugins) { 517 # do not use the reload_plugin method since that 518 # refreshes the menu every time 519 $self->_unload_plugin($name); 520 $self->_load_plugin_norefresh_menu($name); 521 $self->enable_editors($name); 522 } 523 $self->_refresh_plugin_menu(); 524 return 1; 525 } 526 527 =pod 528 529 =head2 alert_new 530 531 The C<alert_new> method is called by the main window post-init and 532 checks for new plugins. If any are found, it presents a message to 533 the user. 534 535 =cut 536 537 sub alert_new { 538 my $self = shift; 539 my $plugins = $self->plugins; 540 541 my $new_plugins = ''; 542 foreach my $name ( sort keys %$plugins ) { 543 if ( $plugins->{$name}->{status} eq 'new' ) { 544 $new_plugins .= "$name\n"; 532 533 534 535 536 ##################################################################### 537 # Enabling and Disabling a Plugin 538 539 # Assume the named plugin exists, enable it 540 sub _plugin_enable { 541 $_[0]->_plugin($_[1])->enable; 542 } 543 544 # Assume the named plugin exists, disable it 545 sub _plugin_disable { 546 $_[0]->_plugin($_[1])->disable; 547 } 548 549 =pod 550 551 =head2 plugin_config 552 553 Given a plugin name or namespace, returns a hash reference 554 which corresponds to the configuration section in the Padre 555 YAML configuration of that plugin. Any modifications of that 556 hash reference will, on normal exit, be written to the 557 configuration file. 558 559 If the plugin name is omitted and this method is called from 560 a plugin namespace, the plugin name is determine automatically. 561 562 =cut 563 564 sub plugin_config { 565 my $self = shift; 566 567 # Infer the plugin name from caller if not provided 568 my $param = shift; 569 unless ( defined $param ) { 570 my ($package) = caller(); 571 unless ( $package =~ /^Padre::Plugin::/ ) { 572 croak("Cannot infer the name of the plugin for which the configuration has been requested"); 545 573 } 546 } 547 if ( $new_plugins and not $ENV{HARNESS_ACTIVE} ) { 548 my $msg = Wx::gettext(<<"END_MSG"); 549 We found several new plugins. 550 In order to configure and enable them go to 551 Plugins -> Plugin Manager 552 553 List of new plugins: 554 555 END_MSG 556 $msg .= $new_plugins; 557 558 $self->parent->wx->main_window->message($msg, Wx::gettext('New plugins detected')); 559 } 560 561 return 1; 562 } 563 564 565 566 567 568 ##################################################################### 569 # Enable and Disable 574 $param = $package; 575 } 576 577 # Get the plugin, and from there he config 578 my $plugin = $self->_plugin($param); 579 my $config = $self->parent->config; 580 return( $config->{plugins} ||= {} ); 581 } 570 582 571 583 # enable all the plugins for a single editor … … 612 624 } 613 625 614 =pod 615 616 =head2 reload_plugin 617 618 Reload a single plugin whose name (without C<Padre::Plugin::>) 619 is passed in as first argument. 620 621 =cut 622 623 sub reload_plugin { 624 my $self = shift; 625 my $name = shift; 626 $self->_unload_plugin($name); 627 $self->load_plugin($name) or return; 628 $self->enable_editors($name) or return; 629 return 1; 630 } 631 632 # refresh the Plugins menu 633 sub _refresh_plugin_menu { 634 my $self = shift; 635 636 $self->parent->wx->main_window->menu->plugins->refresh; 637 } 638 639 =pod 640 641 =head2 failed 642 643 Returns the plugin names (without C<Padre::Plugin::> prefixed) of all plugins 644 that the editor attempted to load but failed. Note that after a failed 645 attempt, the plugin is usually disabled in the configuration and not loaded 646 again when the editor is restarted. 647 648 =cut 649 650 sub failed { 626 627 628 629 630 ###################################################################### 631 # Menu Integration 632 633 # Generate the menu for a plugin 634 sub get_menu { 651 635 my $self = shift; 652 my $plugins = $self->plugins; 653 return grep { 654 $plugins->{$_}->{status} eq 'failed' 655 } keys %$plugins; 636 my $main = shift; 637 my $name = shift; 638 my $plugin = $self->plugins->{$name}; 639 unless ( $plugin and $plugin->{status} eq 'enabled' ) { 640 return (); 641 } 642 unless ( $plugin->{object}->can('menu_plugins') ) { 643 return (); 644 } 645 my ($label, $menu) = eval { 646 $plugin->{object}->menu_plugins($main) 647 }; 648 if ( $@ ) { 649 $self->{errstr} = "Error when calling menu for plugin '$name' $@"; 650 return (); 651 } 652 unless ( defined $label and defined $menu ) { 653 return (); 654 } 655 return ($label, $menu); 656 656 } 657 657 … … 704 704 } 705 705 706 sub get_menu { 707 my $self = shift; 708 my $main = shift; 709 my $name = shift; 710 my $plugin = $self->plugins->{$name}; 711 unless ( $plugin and $plugin->{status} eq 'enabled' ) { 712 return (); 713 } 714 unless ( $plugin->{object}->can('menu_plugins') ) { 715 return (); 716 } 717 my ($label, $menu) = eval { $plugin->{object}->menu_plugins($main) }; 718 if ( $@ ) { 719 $self->{errstr} = "Error when calling menu for plugin '$name' $@"; 720 return (); 721 } 722 unless ( defined $label and defined $menu ) { 723 return (); 724 } 725 return ($label, $menu); 706 # Refresh the Plugins menu 707 sub _refresh_plugin_menu { 708 $_[0]->parent->wx->main_window->menu->plugins->refresh; 709 } 710 711 712 713 714 715 ###################################################################### 716 # Support Functions 717 718 sub _plugin { 719 my ($self, $it) = @_; 720 if ( _INSTANCE($it, 'Padre::PluginHandle') ) { 721 my $current = $self->{plugins}->{$it->name}; 722 unless ( defined $current ) { 723 croak("Unknown plugin '$it' provided to PluginManager"); 724 } 725 unless ( 726 Scalar::Util::refaddr($it) 727 == 728 Scalar::Util::refaddr($current) 729 ) { 730 croak("Duplicate plugin '$it' provided to PluginManager"); 731 } 732 return $it; 733 } 734 if ( defined _CLASS($it) ) { 735 # Convert from class to name if needed 736 $it =~ s/^Padre::Plugin:://; 737 } 738 if ( _IDENTIFIER($it) ) { 739 unless ( defined $self->{plugins}->{$it} ) { 740 croak("Plugin '$it' does not exist in PluginManager"); 741 } 742 return $self->{plugins}->{$it}; 743 } 744 croak("Missing or invalid plugin provided to Padre::PluginManager"); 726 745 } 727 746
Note: See TracChangeset
for help on using the changeset viewer.
