package Plagger; use strict; our $VERSION = '0.5.4'; use 5.8.1; use Carp; use Data::Dumper; use File::Find::Rule; use YAML; use UNIVERSAL::require; use base qw( Class::Accessor::Fast ); __PACKAGE__->mk_accessors( qw(conf update subscription plugins_path) ); use Plagger::Date; use Plagger::Entry; use Plagger::Feed; use Plagger::Subscription; use Plagger::Template; use Plagger::Update; sub active_hooks { my $self = shift; my @hooks= keys %{$self->{hooks}}; wantarray ? @hooks : \@hooks; } sub context { undef } sub bootstrap { my($class, %opt) = @_; my $self = bless { conf => {}, update => Plagger::Update->new, subscription => Plagger::Subscription->new, plugins_path => {}, }, $class; my $config; if (-e $opt{config} && -r _) { $config = YAML::LoadFile($opt{config}); $self->{conf} = $config->{global}; $self->{conf}->{log} ||= { level => 'debug' }; } else { croak "Plagger->bootstrap: $opt{config}: $!"; } local *Plagger::context = sub { $self }; $self->load_plugins(@{ $config->{plugins} || [] }); $self->run(); } sub load_plugins { my($self, @plugins) = @_; if ($self->conf->{plugin_path}) { for my $path (@{ $self->conf->{plugin_path} }) { my $rule = File::Find::Rule->new; $rule->file; $rule->name( qr/^\w[\w\.]*$/ ); my @files = $rule->in($path); for my $file (@files) { next if $file =~ /\W(?:\.svn|CVS)\b/; my $pkg = $self->extract_package($file) or die "Can't find package from $file"; (my $base = $file) =~ s!^$path/!!; $self->plugins_path->{$pkg} = $file; } } } for my $plugin (@plugins) { $self->load_plugin($plugin) unless $plugin->{disable}; } } sub extract_package { my($self, $file) = @_; open my $fh, $file or die "$file: $!"; while (<$fh>) { /^package (Plagger::Plugin::.*?);/ and return $1; } return; } sub load_plugin { my($self, $config) = @_; my $module = delete $config->{module}; $module =~ s/^Plagger::Plugin:://; $module = "Plagger::Plugin::$module"; if (my $path = $self->plugins_path->{$module}) { eval { require $path } or die $@; } else { $module->require or die $@; } $self->log(info => "plugin $module loaded."); my $plugin = $module->new($config); $plugin->register($self); } sub register_hook { my($self, $plugin, @hooks) = @_; while (my($hook, $callback) = splice @hooks, 0, 2) { # set default rule_hook $hook to $plugin $plugin->rule_hook($hook) unless $plugin->rule_hook; push @{ $self->{hooks}->{$hook} }, +{ callback => $callback, plugin => $plugin, }; } } sub run_hook { my($self, $hook, $args) = @_; for my $action (@{ $self->{hooks}->{$hook} }) { my $plugin = $action->{plugin}; if ( $plugin->rule->dispatch($plugin, $hook, $args) ) { $action->{callback}->($plugin, $self, $args); } } } sub run { my $self = shift; $self->run_hook('subscription.load'); for my $type ($self->subscription->types) { for my $feed ($self->subscription->feeds_by_type($type)) { $self->run_hook("aggregator.aggregate.$type", { feed => $feed }); } } $self->run_hook('aggregator.finalize'); for my $feed ($self->update->feeds) { for my $entry ($feed->entries) { $self->run_hook('update.entry.fixup', { feed => $feed, entry => $entry }); } $self->run_hook('update.feed.fixup', { feed => $feed }); } $self->run_hook('update.fixup'); $self->run_hook('smartfeed.init'); for my $feed ($self->update->feeds) { for my $entry ($feed->entries) { $self->run_hook('smartfeed.entry', { feed => $feed, entry => $entry }); } } $self->run_hook('smartfeed.finalize'); $self->run_hook('publish.init'); for my $feed ($self->update->feeds) { for my $entry ($feed->entries) { $self->run_hook('publish.entry.fixup', { feed => $feed, entry => $entry }); } $self->run_hook('publish.feed', { feed => $feed }); for my $entry ($feed->entries) { $self->run_hook('publish.entry', { feed => $feed, entry => $entry }); } } $self->run_hook('publish.finalize'); } sub log { my($self, $level, $msg) = @_; # hack to get the original caller as Plugin or Rule my $caller; my $i = 0; while (my $c = caller($i++)) { last if $c !~ /Plugin|Rule/; $caller = $c; } $caller ||= caller(0); chomp($msg); if ($self->should_log($level)) { warn "$caller [$level] $msg\n"; } } my %levels = ( debug => 0, warn => 1, info => 2, error => 3, ); sub should_log { my($self, $level) = @_; $levels{$level} >= $levels{$self->conf->{log}->{level}}; } sub error { my($self, $msg) = @_; my($caller, $filename, $line) = caller(0); chomp($msg); die "$caller [fatal] $msg at line $line\n"; } sub dumper { my($self, $stuff) = @_; local $Data::Dumper::Indent = 1; $self->log(debug => Dumper($stuff)); } sub template { my $self = shift; $self->{template} ||= Plagger::Template->new($self); } 1; __END__ =head1 NAME Plagger - Pluggable RSS/Atom Aggregator =head1 SYNOPSIS % plagger =head1 DESCRIPTION Plagger is a pluggable RSS/Atom feed aggregator. See L for the latest code, development community and bug tracking. =head1 AUTHOR Tatsuhiko Miyagawa Emiyagawa@bulknews.netE See I file for the name of all the contributors. =head1 LICENSE Except where otherwise noted, Plagger is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L =cut