転職しました。

超久方ぶりの更新です。もう誰も見てる人いないだろうなあ・・・


私事になりますが、タイトルの通り転職しました。
2月初めから新しい会社で働き始めています。


新しい会社は社員20名弱のベンチャーで、オープンソースのソフトウェアを開発したりしてるとこ。
残念ながら言語はPerlじゃなくPHPなんですが、憧れのwebの仕事ができ、
毎日ワクテカする日々を送っております。


今は結構忙しくてなかなか時間が取れませんが
隙を見てぼちぼち更新再開したいものですね。



ベンチャー企業に入社して気づいたこと
ベンチャーの中の人はみんな暗黒面!暗黒面!とか言ってたり、
ライトセイバーを振り回してたりするのかと思ったらそうでもなかった。
でもバランスボールはある。

Plugin::Widget::CPAN

entryのbodyにPerlのモジュールらしきものがあったらCPANへの検索リンクをつけたすWidget

package Plagger::Plugin::Widget::CPAN;
use strict;
use base qw( Plagger::Plugin );

use Encode;
use HTML::Entities;
use URI;

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'publish.entry.fixup' => \&add,
    );
}

sub add {
    my($self, $context, $args) = @_;
    $args->{entry}->add_widget($self);
}

sub html {
    my($self, $entry) = @_;

    return unless ( $entry->body =~ /([a-z]+?::[a-z]+)/i );
    my $module = $1;

    my $uri = URI->new('http://search.cpan.org/search');
    $uri->query_form(
        query => $module,
        mode  => $self->conf->{mode} || 'all',
    );

    my $url = HTML::Entities::encode($uri->as_string);
    return qq(<a href="$url"><img src="http://search.cpan.org/favicon.ico" alt="$module" style="border:0;vertical-align:middle" /></a>);
}

1;
  - module: Widget::CPAN
    config:
      mode: all, module, dist or author # default all


WidgetじゃなくてFilter::CPANにしようかな。
正規表現ももうちょっと勉強しなくちゃ・・・

Pluginとconfigの一覧

/\$self->conf->{(.+?)}/で抜き出しただけなんで
変なのも混じってるし全部取れてないしconfigとmoduleが上下逆で見にくいことこの上ないけど
まあ参考程度に。

plugins: 
  - 
    module: Aggregator::Null
  - 
    module: Aggregator::Simple
  - 
    config: 
      max_redirect: ~
      timeout: ~
      use_cache: ~
      xango_args: ~
    module: Aggregator::Xango
  - 
    module: Bundle::Defaults
  - 
    config: 
      $directive: ~
      description: ~
      dir: ~
      duration: ~
      extra_rule: ~
      scrubber: ~
      stylesheet: ~
      theme: ~
      title: ~
      url: ~
    module: Bundle::Planet
  - 
    module: CustomFeed::2chSearch
  - 
    module: CustomFeed::AmazonAssociateReportJP
  - 
    module: CustomFeed::BloglinesCitations
  - 
    config: 
      $_: ~
      entry: ~
    module: CustomFeed::Debug
  - 
    config: 
      api_key: ~
      method: ~
      params: ~
      size: ~
    module: CustomFeed::FlickrSearch
  - 
    config: 
      feed_type: ~
      fetch_body: ~
      fetch_body_interval: ~
      fetch_items: ~
      livedoor_id: ~
      show_icon: ~
    module: CustomFeed::Frepa
  - 
    module: CustomFeed::GoogleNews
  - 
    config: 
      aws_associate_id: ~
      aws_developer_token: ~
      aws_locale: ~
      duration: ~
      library_path: ~
      title_format: ~
    module: CustomFeed::iTunesRecentPlay
  - 
    config: 
      fetch_items: ~
    module: CustomFeed::Mailman
  - 
    config: 
      email: ~
      feed_type: ~
      fetch_body: ~
      fetch_body_interval: ~
      fetch_items: ~
      password: ~
      show_icon: ~
    module: CustomFeed::Mixi
  - 
    config: 
      mixi_tos_paranoia: ~
    module: CustomFeed::MixiDiarySearch
  - 
    module: CustomFeed::PerlMonks
  - 
    config: 
      delete: ~
      host: ~
      username: ~
    module: CustomFeed::POP3
  - 
    module: CustomFeed::Script
  - 
    module: CustomFeed::Simple
  - 
    config: 
      fetch_items: ~
      link: ~
      reverse: ~
      revision_from: ~
      revision_to: ~
      target: ~
      title: ~
    module: CustomFeed::SVNLog
  - 
    config: 
      page: ~
      query: ~
      sort: ~
    module: CustomFeed::YouTube
  - 
    module: CustomFeed::FriendDiary
  - 
    module: CustomFeed::FriendStatus
  - 
    module: CustomFeed::RecentComment
  - 
    module: Filter::2chNewsokuTitle
  - 
    module: Filter::2chRSSContent
  - 
    module: Filter::AtomLinkRelated
  - 
    config: 
      destination: ~
      prepend_original: ~
      service: ~
      source: ~
    module: Filter::Babelfish
  - 
    config: 
      text_only: ~
    module: Filter::Base
  - 
    module: Filter::BloglinesContentNormalize
  - 
    module: Filter::BlogPet
  - 
    config: 
      use_entry_title: ~
    module: Filter::BreakEntriesToFeeds
  - 
    config: 
      apikey: ~
    module: Filter::BulkfeedsTerms
  - 
    config: 
      link: ~
      title: ~
    module: Filter::CompositeFeed
  - 
    config: 
      interval: ~
      scrape_big_numbers: ~
    module: Filter::Delicious
  - 
    module: Filter::DeliciousFeedTags
  - 
    config: 
      driver: ~
      option: ~
    module: Filter::Emoticon
  - 
    config: 
      force_upgrade: ~
      store_html_on_failure: ~
    module: Filter::EntryFullText
  - 
    module: Filter::ExtractAuthorName
  - 
    module: Filter::FeedBurnerPermalink
  - 
    module: Filter::FeedFlareStripper
  - 
    config: 
      dir: ~
      fake_referer: ~
    module: Filter::FetchEnclosure
  - 
    module: Filter::FindEnclosures
  - 
    config: 
      timezone: ~
    module: Filter::FloatingDateTime
  - 
    config: 
      timezone: ~
    module: Filter::ForceTimeZone
  - 
    config: 
      languages: ~
      target: ~
    module: Filter::GuessLanguage
  - 
    config: 
      conflict_policy: ~
      use_ip_country: ~
    module: Filter::GuessTimeZoneByDomain
  - 
    module: Filter::HatenaBookmarkTag
  - 
    module: Filter::HatenaBookmarkUsersCount
  - 
    module: Filter::HatenaDiaryKeywordLink
  - 
    module: Filter::HatenaDiaryKeywordUnlink
  - 
    module: Filter::HatenaFormat
  - 
    module: Filter::HatenaKeywordTag
  - 
    module: Filter::HEADEnclosureMetadata
  - 
    module: Filter::HTMLScrubber
  - 
    module: Filter::ImageInfo
  - 
    module: Filter::Kansai
  - 
    module: Filter::LivedoorClipUsersCount
  - 
    module: Filter::LivedoorKeywordUnlink
  - 
    module: Filter::Markdown
  - 
    config: 
      command: ~
      encoding: ~
      text_only: ~
      timeout: ~
    module: Filter::Pipe
  - 
    config: 
      encoding: ~
      proxy: ~
      tempdir: ~
      training: ~
    module: Filter::POPFile
  - 
    module: Filter::Profanity
  - 
    config: 
      regexp: ~
    module: Filter::Regexp
  - 
    module: Filter::ResolveRelativeLink
  - 
    config: 
      rewrite: ~
    module: Filter::RewriteEnclosureURL
  - 
    module: Filter::Romanize
  - 
    module: Filter::RSSLiberalDateTime
  - 
    module: Filter::RSSTimeZoneString
  - 
    module: Filter::Rule
  - 
    config: 
      add_report: ~
      new: ~
      spam_tag: ~
    module: Filter::SpamAssassin
  - 
    module: Filter::StripRSSAd
  - 
    module: Filter::StripTagsFromTitle
  - 
    config: 
      add_to: ~
    module: Filter::TagsToTitle
  - 
    module: Filter::tDiaryComment
  - 
    config: 
      set_per_entry: ~
    module: Filter::Thumbnail
  - 
    config: 
      follow_redirect: ~
    module: Filter::TruePermalink
  - 
    module: Filter::TTP
  - 
    config: 
      form: ~
    module: Filter::UnicodeNormalize
  - 
    config: 
      dnsbl: ~
      rate: ~
    module: Filter::URLBL
  - 
    config: 
      dir: ~
      fake_referer: ~
    module: Filter::Curl
  - 
    config: 
      concurrency: ~
      dir: ~
      fake_referer: ~
      max_requests_per_host: ~
    module: Filter::ParallelUA
  - 
    config: 
      dir: ~
      fake_referer: ~
    module: Filter::Wget
  - 
    config: 
      dir: ~
      fake_referer: ~
      max_redirect: ~
      timeout: ~
      use_cache: ~
      xango_args: ~
    module: Filter::Xango
  - 
    module: Filter::Japanese
  - 
    module: Namespace::ApplePhotocast
  - 
    module: Namespace::HatenaFotolife
  - 
    module: Namespace::MediaRSS
  - 
    config: 
      filename: ~
      play_enclosures: ~
      player: ~
    module: Notify::Audio
  - 
    config: 
      encoding: ~
    module: Notify::Balloon
  - 
    config: 
      music: ~
    module: Notify::Beep
  - 
    config: 
      email: ~
      speak_interval: ~
    module: Notify::Campfire
  - 
    config: 
      command: ~
    module: Notify::Command
  - 
    module: Notify::Eject
  - 
    module: Notify::Growl
  - 
    config: 
      charset: ~
      daemon_host: ~
      daemon_port: ~
    module: Notify::IRC
  - 
    config: 
      animation: ~
      character: ~
      position: ~
      voice: ~
    module: Notify::MSAgent
  - 
    config: 
      source_netbios_name: ~
      target_ip: ~
      target_netbios_name: ~
    module: Notify::NetSend
  - 
    module: Notify::OpenBrowser
  - 
    config: 
      host: ~
      options: ~
      port: ~
    module: Notify::SSTP
  - 
    config: 
      channel: ~
      charset: ~
      send_interval: ~
      sender: ~
      socketname: ~
      use_notice: ~
    module: Notify::Tiarra
  - 
    config: 
      extended_ping: ~
      url: ~
    module: Notify::UpdatePing
  - 
    module: Notify::iTunesMac
  - 
    module: Notify::iTunesWin32
  - 
    module: Notify::MSWin32
  - 
    module: Notify::darwin
  - 
    module: Notify::freebsd
  - 
    module: Notify::linux
  - 
    module: Notify::mswin32
  - 
    module: Notify::darwin
  - 
    config: 
      host: ~
      port: ~
    module: Notify::FirefoxRemote
  - 
    module: Notify::freebsd
  - 
    module: Notify::linux
  - 
    module: Notify::mswin32
  - 
    config: 
      default_anonymous: ~
      dir: ~
    module: Publish::2chdat
  - 
    config: 
      encoding: ~
      title: ~
      work: ~
    module: Publish::CHTML
  - 
    config: 
      column: ~
      dir: ~
      encoding: ~
      filename: ~
      mode: ~
    module: Publish::CSV
  - 
    config: 
      expression: ~
    module: Publish::Debug
  - 
    config: 
      interval: ~
      password: ~
      post_body: ~
      username: ~
    module: Publish::Delicious
  - 
    config: 
      filename: ~
    module: Publish::Excel
  - 
    config: 
      dir: ~
      filename: ~
      full_content: ~
    module: Publish::Feed
  - 
    config: 
      filename: ~
    module: Publish::FOAFRoll
  - 
    config: 
      attach_enclosures: ~
      mailfrom: ~
      mailto: ~
      pop3: ~
    module: Publish::Gmail
  - 
    config: 
      interval: ~
      password: ~
      post_body: ~
      username: ~
    module: Publish::HatenaBookmark
  - 
    config: 
      dir: ~
      filename: ~
    module: Publish::iCal
  - 
    config: 
      folder: ~
    module: Publish::IMAP
  - 
    config: 
      dir: ~
      filename: ~
    module: Publish::JavaScript
  - 
    config: 
      dir: ~
      filename: ~
      jsonp: ~
      varname: ~
    module: Publish::JSON
  - 
    config: 
      interval: ~
      livedoor_id: ~
      password: ~
      post_body: ~
    module: Publish::LivedoorClip
  - 
    config: 
      attach_enclosures: ~
    module: Publish::Maildir
  - 
    config: 
      blog_id: ~
      password: ~
      rsd: ~
      title: ~
      username: ~
    module: Publish::MT
  - 
    config: 
      blog_id: ~
      mt_path: ~
      rebuild: ~
      title: ~
    module: Publish::MTWidget
  - 
    config: 
      filename: ~
    module: Publish::OPML
  - 
    config: 
      encoding: ~
      filename: ~
    module: Publish::OutlineText
  - 
    config: 
      medoc: ~
      path: ~
      prefix: ~
    module: Publish::PalmDoc
  - 
    config: 
      dir: ~
      filename: ~
    module: Publish::PDF
  - 
    config: 
      command: ~
      encoding: ~
    module: Publish::Pipe
  - 
    config: 
      dir: ~
      template: ~
      theme: ~
    module: Publish::Planet
  - 
    config: 
      password: ~
      username: ~
    module: Publish::Playlog
  - 
    config: 
      dir: ~
    module: Publish::PowerPoint
  - 
    config: 
      output_file: ~
    module: Publish::PSP
  - 
    config: 
      dir: ~
      filename: ~
      option: ~
      serializer: ~
    module: Publish::Serializer
  - 
    module: Publish::Speech
  - 
    config: 
      bgcolor: ~
      body_size: ~
      color: ~
      dir: ~
      font: ~
      height: ~
      linefeed: ~
      title_size: ~
      width: ~
    module: Publish::SWF
  - 
    config: 
      dir: ~
    module: Publish::Takahashi
  - 
    config: 
      pitch: ~
      speed: ~
      voice: ~
    module: Publish::MacOSX
  - 
    config: 
      voice: ~
    module: Publish::Win32
  - 
    config: 
      debug: ~
      password: ~
      timeout: ~
      url: ~
      username: ~
    module: Search::Estraier
  - 
    config: 
      dir: ~
    module: Search::Grep
  - 
    config: 
      invindex: ~
    module: Search::KinoSearch
  - 
    config: 
      dir: ~
      index: ~
      mknmz_opt: ~
    module: Search::Namazu
  - 
    config: 
      dir: ~
      encode: ~
      replace: ~
    module: Search::Rast
  - 
    config: 
      add_comment: ~
      dir: ~
    module: Search::Spotlight
  - 
    config: 
      description: ~
      id: ~
      link: ~
      title: ~
    module: SmartFeed::All
  - 
    config: 
      description: ~
      id: ~
      link: ~
      title: ~
    module: SmartFeed
  - 
    config: 
      url: ~
    module: Subscription::2chThreadList
  - 
    config: 
      fetch_meta: ~
      mark_read: ~
      no_sync_api: ~
      password: ~
      username: ~
    module: Subscription::Bloglines
  - 
    config: 
      browser: ~
      path: ~
    module: Subscription::Bookmarks
  - 
    config: 
      feed: ~
    module: Subscription::Config
  - 
    config: 
      connect_info: ~
      schema_class: ~
    module: Subscription::DBI
  - 
    config: 
      url: ~
    module: Subscription::Feed
  - 
    config: 
      file: ~
    module: Subscription::File
  - 
    config: 
      url: ~
    module: Subscription::FOAF
  - 
    config: 
      group: ~
    module: Subscription::HatenaGroup
  - 
    config: 
      password: ~
      username: ~
    module: Subscription::HatenaRSS
  - 
    config: 
      mark_read: ~
      password: ~
      username: ~
    module: Subscription::LivedoorReader
  - 
    config: 
      account: ~
    module: Subscription::Odeo
  - 
    config: 
      url: ~
    module: Subscription::OPML
  - 
    config: 
      fetch_items: ~
      servers: ~
    module: Subscription::PingServer
  - 
    config: 
      $key: ~
      feed: ~
      lang: ~
    module: Subscription::Planet
  - 
    config: 
      path: ~
    module: Subscription::PlanetINI
  - 
    config: 
      xpath: ~
    module: Subscription::XOXO
  - 
    config: 
      url: ~
      xpath: ~
    module: Subscription::XPath
  - 
    module: Subscription::InternetExplorer
  - 
    config: 
      url: ~
    module: Subscription::Mozilla
  - 
    config: 
      path: ~
    module: Subscription::Safari
  - 
    module: Summary::Auto
  - 
    module: Summary::English
  - 
    module: Summary::HatenaBookmark
  - 
    module: Summary::Simple
  - 
    module: Summary::TextOriginal
  - 
    module: Summary::TrackbackRDF
  - 
    config: 
      credentials: ~
    module: UserAgent::AuthenRequest
  - 
    config: 
      $header: ~
    module: UserAgent::RequestHeader
  - 
    module: Widget::BloglinesSubscription
  - 
    module: Widget::BulkfeedsSpamReport
  - 
    config: 
      one_click_post: ~
      tags: ~
    module: Widget::Delicious
  - 
    module: Widget::HatenaBookmark
  - 
    config: 
      image_size: ~
      use_filter: ~
    module: Widget::HatenaBookmarkUsersCount
  - 
    config: 
      widget: ~
    module: Widget::Simple

Plugin::PhaseNotifier(2)

AUTOLOAD使わずに書き直してみた。
これでいいのかなー。

package Plagger::Plugin::PhaseNotifier;
use strict;
use base qw( Plagger::Plugin );

sub register {
    my($self, $context) = @_;

    my @hooks;
    for my $hook ( keys %{ $context->{hooks} } ) {

        my @plugins = map {
            my $plugin = ref $_->{plugin};
               $plugin =~ s/^Plagger::Plugin:://;
               $plugin;
        } @{ $context->{hooks}->{$hook} };
        
        my $callback = sub {
            my($self, $context, $args) = @_;

            my $msg = "Phase: $hook / Plugins: " . (join ', ', @plugins);
            $context->log(info => $msg);
        };
            
        push @hooks, $hook => $callback;
    }

    $context->register_hook(
        $self,
        @hooks,
    );
}

1;

あとはAggregater::Simpleのcustomfeed.handleをどうやって取るかだなあ。

Plugin::PhaseNotifier

なんとなく作ってみた。
現在のフェーズとそのフェーズで使われるPluginを教えてくれるPlugin。


こういう風にconfig.yaml書くと、

  - module: Subscription::Config
    config:
      feed: 
        - url: http://d.hatena.ne.jp/tayaya/rss
  - module: Publish::Gmail
  - module: Notify::YahooMessenger
  - module: PhaseNotifier

下のようにフェーズとPlugin名が表示される。

Plagger [info] plugin Plagger::Plugin::Subscription::Config loaded.
Plagger [info] plugin Plagger::Plugin::Publish::Gmail loaded.
Plagger [info] plugin Plagger::Plugin::Notify::YahooMessenger loaded.
Plagger [info] plugin Plagger::Plugin::PhaseNotifier loaded.
Plagger::Plugin::PhaseNotifier [info] Phase: subscription.load / Plugins: Subscription::Config
Plagger [info] plugin Plagger::Plugin::Aggregator::Simple loaded.
・
・
・
Plagger::Plugin::Publish::Gmail [info] Sending qw/Perる 日誌/ to xxx@example.com
Plagger::Plugin::Notify::YahooMessenger [info] Notifying qw/Perる 日誌/ to Yahoo Messenger
Plagger::Plugin::PhaseNotifier [info] Phase: publish.feed / Plugins: Publish::Gmail, Notify::YahooMessenger

ソースはこんな感じ。

package Plagger::Plugin::PhaseNotifier;
use strict;
use base qw( Plagger::Plugin );

sub register {
    my($self, $context) = @_;

    my @hooks;
    for my $hook ( keys %{ $context->{hooks} } ) {

        my @plugins = map {
            my $plugin = ref $_->{plugin};
               $plugin =~ s/^Plagger::Plugin:://;
               $plugin;
        } @{ $context->{hooks}->{$hook} };
        
        push @{ $self->{hooks}->{$hook} }, @plugins;
        push @hooks, $hook => \&$hook;
    }

    $context->register_hook(
        $self,
        @hooks,
    );
}

sub AUTOLOAD {
    my($self, $context, $args) = @_;

    (my $phase = our $AUTOLOAD) =~ s/^Plagger::Plugin::PhaseNotifier:://;
    my @plugins = @{ $self->{hooks}->{$phase} };

    $context->log( info => "Phase: $phase / Plugins: " . (join ', ', @plugins) );
    $context->dumper($context) if $self->conf->{context};
    $context->dumper($args)    if $self->conf->{args};
}   

1;

Publish::OOoWriter

OpenOffice.org Writerに出力するPlugin。
つくりかけ。

package Plagger::Plugin::Publish::OOoWriter;
use strict;
use base qw(Plagger::Plugin);

our $Version = '0.01';

use OpenOffice::OODoc;

ooLocalEncoding('UTF-8');

sub register {
    my ($self, $context, $args) = @_;
    $context->register_hook(
        $self,
        'publish.feed' => \&feed,
    );
}

sub feed {
    my ($self, $context, $args) = @_;

    my $body = $self->templatize('oo.tt', { feed => $args->{feed} });
    Encode::_utf8_off($body) if Encode::is_utf8($body);

    my $filename = $args->{feed}->title . '.odt';

    my $oo = ooDocument(
        file   => $filename,
        create => 'text'
    );
    $oo->appendParagraph(text => $body);
    $oo->save;

    $context->log(info => "Save $filename");
}

1;

oo.tt

[% feed.title_text %]

[% FOREACH entry = feed.entries %]
[% entry.title_text %]
Link: [% entry.permalink %]
by [% entry.author %]
[% entry.body_text %]
Posted on [% entry.date.format('Mail') %]


[% END %]


TODO
OpenOffice::OODocの使い方を調べる。
文字コード関連の処理がよくわからんので調べる。
テンプレート使わずに出力できるようにする。