<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CEcAQ387cCp7ImA9WhVWEEg.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792</id><updated>2012-04-22T03:47:22.108+04:00</updated><category term="DBIx::Class" /><category term="Twitter" /><category term="Gearman::Driver" /><category term="Javascript" /><category term="Schorndorf" /><category term="cpan" /><category term="Pithub" /><category term="irssi" /><category term="dotCloud" /><category term="POE" /><category term="Perl" /><category term="Gearman" /><category term="xing" /><category term="Catalyst" /><category term="Firebug" /><category term="DBIC" /><category term="GPW" /><category term="git" /><category term="Mojolicious" /><category term="local::lib" /><category term="GPW2010" /><category term="MetaCPAN" /><category term="Bash" /><category term="MojoMojo" /><category term="Github" /><category term="ExtJS" /><title>Johannes Plunien</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.pqpq.de/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.pqpq.de/" /><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/12711971246723349642</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>16</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/plu" /><feedburner:info uri="plu" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;CUQBSH4_fyp7ImA9WhdSE08.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-7674019403181821284</id><published>2011-07-22T12:13:00.006+04:00</published><updated>2011-07-22T12:29:19.047+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-22T12:29:19.047+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Mojolicious" /><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="Github" /><category scheme="http://www.blogger.com/atom/ns#" term="cpan" /><category scheme="http://www.blogger.com/atom/ns#" term="Pithub" /><category scheme="http://www.blogger.com/atom/ns#" term="MetaCPAN" /><category scheme="http://www.blogger.com/atom/ns#" term="dotCloud" /><title>Github Meets CPAN</title><content type="html">When &lt;a href="http://blogs.perl.org/users/olaf_alders/"&gt;Olaf Alders&lt;/a&gt; was writing about &lt;a href="http://blogs.perl.org/users/olaf_alders/2011/07/how-to-expand-your-metacpan-profile.html"&gt;expanding your MetaCPAN profile&lt;/a&gt; he suggested to build something interesting on top of the &lt;a href="https://metacpan.org/"&gt;MetaCPAN&lt;/a&gt; &lt;a href="https://github.com/CPAN-API/cpan-api/wiki/Beta-API-docs"&gt;API&lt;/a&gt;. When I started writing &lt;a href="https://metacpan.org/module/Pithub"&gt;Pithub&lt;/a&gt; I did not have any use case in mind at all. I just wrote it because I was bored and because there was no Perl support yet for the v3 of the &lt;a href="https://github.com/"&gt;Github&lt;/a&gt; API. When I read Olaf's article, I got an idea: Connect the CPAN authors listed by the MetaCPAN API to their Github data, in case they have their Github account set. And there it was: The first use case for Pithub.
&lt;br/&gt;
The page is up and running on &lt;a href="http://dotcloud.com/"&gt;dotCloud&lt;/a&gt; and you can check it out here: &lt;a href="http://www.github-meets-cpan.com/"&gt;http://www.github-meets-cpan.com/&lt;/a&gt;
&lt;br/&gt;
The whole thing was kind of boring until Olaf had yet another great idea: Add some ranking there and sort the users by it, instead of sorting them just alphabetically. Currently the rank is calculated by this formula:
&lt;br/&gt;
&lt;pre&gt;  (count of your followers)
+ (sum of watchers of all of your repositories)
+ (sum of forks of all of your repositories)
= rank&lt;/pre&gt;
&lt;br/&gt;
It's also available on Github: &lt;a href="https://github.com/plu/github-meets-cpan"&gt;https://github.com/plu/github-meets-cpan&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-7674019403181821284?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/ATbnD0TG7p4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/7674019403181821284/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2011/07/github-meets-cpan.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/7674019403181821284?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/7674019403181821284?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/ATbnD0TG7p4/github-meets-cpan.html" title="Github Meets CPAN" /><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/12711971246723349642</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://www.pqpq.de/2011/07/github-meets-cpan.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUANQ3g6eip7ImA9WhdSEEU.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-2363490869366024288</id><published>2011-07-19T18:04:00.004+04:00</published><updated>2011-07-19T19:03:12.612+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-19T19:03:12.612+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="Github" /><category scheme="http://www.blogger.com/atom/ns#" term="cpan" /><category scheme="http://www.blogger.com/atom/ns#" term="Pithub" /><title>Pithub: How to commit a new file via the Github v3 API</title><content type="html">In my &lt;a href="http://www.pqpq.de/2011/07/pithub-how-to-create-new-download-via.html"&gt;last article&lt;/a&gt; I was demonstrating how to create a new download using &lt;a href="http://github.com/plu/Pithub"&gt;Pithub&lt;/a&gt;. Today I will show you how to commit a new file to a repository. Github provides a &lt;a href="http://developer.github.com/v3/git/"&gt;low level API&lt;/a&gt;, which can be used to work directly with git objects like blobs, trees and commits. Committing a file needs several steps:
&lt;ol&gt;
&lt;li&gt;create a new blob with the content of the file&lt;/li&gt;
&lt;li&gt;get the SHA the current master branch points to&lt;/li&gt;
&lt;li&gt;fetch the tree this SHA belongs to&lt;/li&gt;
&lt;li&gt;create a new tree object with the new blob, based on the old tree&lt;/li&gt;
&lt;li&gt;create a new commit object using the new tree and point its parent to the current master&lt;/li&gt;
&lt;li&gt;finally update the heads/master reference to point to the new commit&lt;/li&gt;
&lt;/ol&gt;
You can also read up the explanation in different words on the &lt;a href="http://developer.github.com/v3/git/"&gt;Github API documentation&lt;/a&gt;. If you want to read more about the git internals and how everything works together, please check the Pro Git book, &lt;a href="http://progit.org/book/ch9-0.html"&gt;Chapter 9: Git Internals&lt;/a&gt;.

Ready? One note before: I'm skipping some validation code here, just to make it easier to read. In the full &lt;a href="https://github.com/plu/Pithub/blob/master/examples/gitdata_commit.pl"&gt;example script&lt;/a&gt; the validation code is present. Let's take a look at step 1: create a new blob with the content of the file:

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl
use strict;
use warnings;
use File::Slurp;
use Pithub::GitData;

my $git = Pithub::GitData-&gt;new(
    repo  =&gt; 'Pithub',
    token =&gt; 'your secret token',
    user  =&gt; 'plu',
);

my $content = File::Slurp::read_file(__FILE__);

# the encoding can also be 'base64', if necessary
my $blob = $git-&gt;blobs-&gt;create(
    data =&gt; {
        content  =&gt; $content,
        encoding =&gt; 'utf-8',
    }
);&lt;/pre&gt;

That was easy. We just used File::Slurp to read the content of the current file and create a new blob with that content. The encoding is set to utf-8 because the whole content can be represented in utf-8 without loosing any information. If you want to create a binary file, you need to encode the content in base64 and set the encoding accordingly.

The next steps are to get the SHA the current master branch points to and the tree this SHA belongs to:
&lt;pre class="sh_perl"&gt;my $master = $git-&gt;references-&gt;get( ref =&gt; 'heads/master' );

my $base_commit = $git-&gt;commits-&gt;get( sha =&gt; $master-&gt;content-&gt;{object}{sha} );&lt;/pre&gt;

So $base_commit represents the last commit in the master branch. It also has the information (SHA) to which tree this commit belongs to. This SHA is necessary to create a new tree object (see the base_tree key):

&lt;pre class="sh_perl"&gt;my $tree = $git-&gt;trees-&gt;create(
    data =&gt; {
        base_tree =&gt; $base_commit-&gt;content-&gt;{tree}{sha},
        tree      =&gt; [
            {
                path =&gt; 'examples/gitdata_commit.pl',
                mode =&gt; '100755',
                type =&gt; 'blob',
                sha  =&gt; $blob-&gt;content-&gt;{sha},
            }
        ],
    }
);&lt;/pre&gt;

Once the new tree has been created, we can finally create a new commit and point the master branch (or better: the heads/master reference) to it:

&lt;pre class="sh_perl"&gt;my $commit = $git-&gt;commits-&gt;create(
    data =&gt; {
        message =&gt; 'Add examples/gitdata_commit.pl.',
        parents =&gt; [ $master-&gt;content-&gt;{object}{sha} ],
        tree    =&gt; $tree-&gt;content-&gt;{sha},
    }
);

my $reference = $git-&gt;references-&gt;update(
    ref  =&gt; 'heads/master',
    data =&gt; { sha =&gt; $commit-&gt;content-&gt;{sha} }
);&lt;/pre&gt;

This example &lt;a href="https://github.com/plu/Pithub/blob/master/examples/gitdata_commit.pl"&gt;gitdata_commit.pl&lt;/a&gt; really &lt;a href="https://github.com/plu/Pithub/commit/12c247924e459dbb364873eac6eac9fe190352c3"&gt;committed itself&lt;/a&gt;. In the current version you'll notice a few small differences, because I have changed it since then.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-2363490869366024288?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/3ChhDFuv0Us" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/2363490869366024288/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2011/07/pithub-how-to-commit-new-file-via.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2363490869366024288?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2363490869366024288?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/3ChhDFuv0Us/pithub-how-to-commit-new-file-via.html" title="Pithub: How to commit a new file via the Github v3 API" /><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/12711971246723349642</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2011/07/pithub-how-to-commit-new-file-via.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0QASXoyfSp7ImA9WhdTF0Q.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-1453872126225073095</id><published>2011-07-16T08:17:00.003+04:00</published><updated>2011-07-16T08:42:28.495+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-16T08:42:28.495+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="git" /><category scheme="http://www.blogger.com/atom/ns#" term="Github" /><category scheme="http://www.blogger.com/atom/ns#" term="cpan" /><category scheme="http://www.blogger.com/atom/ns#" term="Pithub" /><title>Pithub: How to create a new download via the Github v3 API</title><content type="html">Uploading a file to Github is a two step process: First you need to create a download resource. After that you can actually upload the file (to Amazon S3). All that is handled for you by Pithub. If you create new TRIAL release using Dist::Zilla, you can use Pithub to provide that file in the download section of your repository at Github. It would be handy to have this as a Dist::Zilla plugin.

The following script makes three assumptions: First: You created that file Pithub-0.01005-TRIAL.tar.gz already via Dist::Zilla, the script does not do that for you. Second: The script needs to be in the same folder as the tar.gz archive. You have an OAuth access token.

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl
use strict;
use warnings;
use Pithub::Repos::Downloads;

my $download = Pithub::Repos::Downloads-&gt;new(
    repo  =&gt; 'Pithub',
    token =&gt; 'my secret token',
    user  =&gt; 'plu',
);

my $result = $download-&gt;create(
    data =&gt; {
        name         =&gt; 'Pithub-0.01005-TRIAL.tar.gz',
        size         =&gt; ( stat('Pithub-0.01005-TRIAL.tar.gz') )[7],
        description  =&gt; 'Pithub v0.01005 TRIAL',
        content_type =&gt; 'application/x-gzip',
    },
);

if ( $result-&gt;success ) {
    my $upload = $download-&gt;upload(
        result =&gt; $result,
        file   =&gt; 'Pithub-0.01005-TRIAL.tar.gz',
    );
    if ( $upload-&gt;is_success ) {
        printf "The file has been uploaded succesfully and is now available at: %s\n", $result-&gt;content-&gt;{html_url};
    }
}&lt;/pre&gt;

Here are some links that might help you:
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://metacpan.org/module/Pithub::Repos::Downloads#create"&gt;http://metacpan.org/module/Pithub::Repos::Downloads#create&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://metacpan.org/module/Pithub::Repos::Downloads#upload"&gt;http://metacpan.org/module/Pithub::Repos::Downloads#upload&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://developer.github.com/v3/oauth/"&gt;http://developer.github.com/v3/oauth/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://developer.github.com/v3/repos/downloads/"&gt;http://developer.github.com/v3/repos/downloads/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-1453872126225073095?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/tRoo2Na7Yxc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/1453872126225073095/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2011/07/pithub-how-to-create-new-download-via.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/1453872126225073095?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/1453872126225073095?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/tRoo2Na7Yxc/pithub-how-to-create-new-download-via.html" title="Pithub: How to create a new download via the Github v3 API" /><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/12711971246723349642</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2011/07/pithub-how-to-create-new-download-via.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08CQX06fCp7ImA9WhdTF08.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-306418771117902192</id><published>2011-07-15T14:54:00.008+04:00</published><updated>2011-07-15T15:37:40.314+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-15T15:37:40.314+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="git" /><category scheme="http://www.blogger.com/atom/ns#" term="Github" /><category scheme="http://www.blogger.com/atom/ns#" term="cpan" /><title>Github v3 API: Pithub</title><content type="html">Almost one month ago &lt;a href="https://github.com"&gt;Github&lt;/a&gt; &lt;a href="https://github.com/blog/879-api-v3-190-methods-to-build-on"&gt;released&lt;/a&gt; a new version of their API: The &lt;a href="http://developer.github.com/v3/"&gt;Github v3 API&lt;/a&gt;. I've checked the CPAN and found &lt;a href="http://beta.metacpan.org/module/Net::GitHub"&gt;Net::Github&lt;/a&gt; there, which supports v2 and v1 of the API so far. From my understanding, the Net:: modules family should be somehow lightweight, please correct me if I'm wrong. They should not do more than providing an interface to the API, which is exactly what &lt;a href="http://beta.metacpan.org/module/Net::GitHub"&gt;Net::Github&lt;/a&gt; does in a very good way. But I was looking for something else, something that does more than this and something that supports the new v3 API. That's why I wrote &lt;a href="http://beta.metacpan.org/module/Pithub"&gt;Pithub&lt;/a&gt; (&lt;a href="https://github.com/plu/Pithub"&gt;also available on Github&lt;/a&gt;). The API should be almost stable now. There are two kind of test suites included: One is not sending any requests to the Github API at all. It is just verifying that the methods are generating correct HTTP requests. Then there's the second test suite: It's sending real requests to the live Github API. It's covering around 90% of all available API calls, just heading to reach the 100% in the next weeks. Running on the live API can generate a lot of activity in your (and especially your contacts) newsfeed. So if you're eager to contribute to this project and you want to run the live tests, I highly recommend that you setup a test account for that. By default the live testsuite is disabled, via environment variables.

The absolute maximum of entries, that are returned per API call, is limited to 100 (the default is 30). If you want to list all repositories of a user, who has more than 100 repositories, you need more than one API call for that. Instead of doing this manually, you can tell &lt;a href="http://beta.metacpan.org/module/Pithub"&gt;Pithub&lt;/a&gt; to take care of that by using its &lt;a href="http://beta.metacpan.org/module/Pithub::Result#auto_pagination"&gt;auto pagination feature&lt;/a&gt;:

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl

use strict;
use warnings;
use Pithub::Repos;

my $r = Pithub::Repos-&gt;new(
    per_page        =&gt; 100,
    auto_pagination =&gt; 1,
);
my $result = $r-&gt;list( user =&gt; 'rjbs' );

unless ( $result-&gt;success ) {
    printf "something is fishy: %s\n", $result-&gt;response-&gt;status_line;
    exit 1;
}

while ( my $row = $result-&gt;next ) {
    printf "%s: %s\n", $row-&gt;{name}, $row-&gt;{description} || 'no description';
}&lt;/pre&gt;

Right now in the second I'm writing this blog post, &lt;a href="https://github.com/rjbs"&gt;rjbs&lt;/a&gt;++ has 318 public repositories. Running the script listed above will make four requests to the Github API:
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://api.github.com/users/rjbs/repos?per_page=100&amp;page=1"&gt;https://api.github.com/users/rjbs/repos?per_page=100&amp;page=1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.github.com/users/rjbs/repos?per_page=100&amp;page=1"&gt;https://api.github.com/users/rjbs/repos?per_page=100&amp;page=2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.github.com/users/rjbs/repos?per_page=100&amp;page=1"&gt;https://api.github.com/users/rjbs/repos?per_page=100&amp;page=3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.github.com/users/rjbs/repos?per_page=100&amp;page=1"&gt;https://api.github.com/users/rjbs/repos?per_page=100&amp;page=4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

Probably nobody else except me is going to use this library, because there's &lt;a href="http://beta.metacpan.org/module/Net::GitHub"&gt;Net::Github&lt;/a&gt; already and I bet it's going to support v3 of the API any time soon as well. Even though it was (and is) just fun for me writing it :). I'll write some more about it in the coming days/weeks...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-306418771117902192?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/rZ73VGcv30A" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/306418771117902192/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2011/07/github-v3-api-pithub.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/306418771117902192?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/306418771117902192?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/rZ73VGcv30A/github-v3-api-pithub.html" title="Github v3 API: Pithub" /><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/12711971246723349642</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://www.pqpq.de/2011/07/github-v3-api-pithub.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UDSX4zfSp7ImA9WhZaGUs.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-9129309223166079571</id><published>2011-07-06T20:08:00.006+04:00</published><updated>2011-07-06T20:21:18.085+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-06T20:21:18.085+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="cpan" /><category scheme="http://www.blogger.com/atom/ns#" term="local::lib" /><category scheme="http://www.blogger.com/atom/ns#" term="Bash" /><title>local::lib magic</title><content type="html">&lt;pre class="sh_sourceCode"&gt;plu@localhost ~[master]$ cd ~/Development/Private/Pithub/
Setting local::lib to: /Volumes/perlbrew/locallib/pithub-v5.12.4

plu@localhost ~/Development/Private/Pithub[master]$ cd ~/Development/Private/metacpan-web/
Setting local::lib to: /Volumes/perlbrew/locallib/metacpanweb-v5.12.4


plu@localhost ~[master]$ cat ~/Development/Private/Pithub/.llrc
/Volumes/perlbrew/locallib/pithub

plu@localhost ~[master]$ cat ~/Development/Private/metacpan-web/.llrc 
/Volumes/perlbrew/locallib/metacpanweb
&lt;/pre&gt;

Yes, you can trust your eyes, the local::lib path is set automatically after the directory has been changed. A similar magic is already built into the &lt;a href="https://rvm.beginrescueend.com/"&gt;Ruby Version Manager&lt;/a&gt;. They just override/wrap the Bash builtin "cd" with a custom function that checks for the existence of a .rvmrc file in the current directory. Why not do the same for local::lib? Unfortunately the RVM script is not very careful and it just sets the cd function, no matter if there was any defined before or not. I'm trying to be more careful in my script. So if you're using RVM, make sure to load my script after the one provided by RVM. I'm quite sure my script is not perfect, so feel free to &lt;a href="https://gist.github.com/1062138"&gt;improve or modify it&lt;/a&gt;!

&lt;pre class="sh_sh"&gt;if [ "$(type -t cd)" == "builtin" ]; then
    local func="_perl_locallib_orig_cd() { builtin cd \"\$@\"; }"
else
    local func="$(declare -f cd)"
    local func="_perl_locallib_orig_cd${func#cd}"
fi
eval $func

_perl_locallib_custom_cd() {
    local cwd=$(pwd)
    if [[ "$HOME" != "$cwd" &amp;&amp; -f "$cwd/.llrc" ]] ; then
        local ll=$(cat $cwd/.llrc)-$(perl -e 'print $^V')
        local OLD_PATH=$PERL_LOCAL_LIB_ROOT
        eval $(perl -Mlocal::lib='--deactivate-all' 2&gt;/dev/null)
        eval $(perl -Mlocal::lib=$ll 2&gt;/dev/null)
        if [ "$?" -eq "0" ] &amp;&amp; [ "$ll" != "$OLD_PATH" ] &amp;&amp; [ -n "$PERL_LOCAL_LIB_ROOT" ]; then
            echo "Setting local::lib to: $ll"
        fi
    else
        if [[ -f "$HOME/.llrc" ]] ; then
            local ll=$(cat $HOME/.llrc)-$(perl -e 'print $^V')
            local OLD_PATH=$PERL_LOCAL_LIB_ROOT
            eval $(perl -Mlocal::lib='--deactivate-all' 2&gt;/dev/null)
            eval $(perl -Mlocal::lib=$ll 2&gt;/dev/null)
            if [ "$?" -eq "0" ] &amp;&amp; [ "$ll" != "$OLD_PATH" ] &amp;&amp; [ -n "$PERL_LOCAL_LIB_ROOT" ]; then
                echo "Setting local::lib to: $ll"
            fi
        fi
    fi
}

cd() {
    _perl_locallib_orig_cd "$@"
    local result=$?
    if [ "$result" -eq "0" ]; then
        _perl_locallib_custom_cd "$@"
    fi
    return $result
}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-9129309223166079571?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/GhMJLeSnZi0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/9129309223166079571/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2011/07/locallib-magic.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/9129309223166079571?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/9129309223166079571?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/GhMJLeSnZi0/locallib-magic.html" title="local::lib magic" /><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/12711971246723349642</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://www.pqpq.de/2011/07/locallib-magic.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak4HRnczfSp7ImA9WhZaGUs.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-8485012552381150294</id><published>2011-07-05T20:09:00.004+04:00</published><updated>2011-07-06T20:15:37.985+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-06T20:15:37.985+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="cpan" /><title>Case insensitive filesystems</title><content type="html">On Windows you have no choice, you get a case insensitive filesystem no matter what and on Mac OS you get the case insensitive version of HFS by default. This might get you into trouble if you intend to use &lt;a href="http://search.cpan.org/dist/File-Stat"&gt;File::Stat&lt;/a&gt; and &lt;a href="http://search.cpan.org/~jesse/perl-5.14.1/lib/File/stat.pm"&gt;File::stat&lt;/a&gt;. I had exactly this problem recently on my Mac OS. Of course I could reinstall the whole operating system on the case sensitive version of HFS and get rid of that problem. But some search results on Google told me that's not such a good idea because it might break some Mac OS applications. Another option would be any kind of a virtual machine running Linux for example, but that's just too much for my (anyway slow) Macbook Air. Finally I found &lt;a href="http://codesnippets.joyent.com/posts/show/8617"&gt;an article&lt;/a&gt; on how to create a self growing image that can have the case sensitive HFS filesystem. So I've created a 10GB image called perlbrew. Once that was done and mounted I told &lt;a href="http://search.cpan.org/dist/App-perlbrew"&gt;perlbrew&lt;/a&gt; to install itself to /Volumes/perlbrew. Another advantage: I can even share that image via &lt;a href="https://www.dropbox.com/"&gt;Dropbox&lt;/a&gt; between my Macs. Two short bash aliases and I was done and happy:

&lt;pre class="sh_sourceCode"&gt;alias mountpb='hdiutil attach -nobrowse ~/Dropbox/Development/perlbrew.sparseimage'
alias umountpb='diskutil umount /Volumes/perlbrew'&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-8485012552381150294?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/twCilB_by3Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/8485012552381150294/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2011/07/case-insensitive-filesystems.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/8485012552381150294?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/8485012552381150294?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/twCilB_by3Y/case-insensitive-filesystems.html" title="Case insensitive filesystems" /><author><name>Johannes Plunien</name><uri>http://www.blogger.com/profile/12711971246723349642</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2011/07/case-insensitive-filesystems.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UHSHc9fSp7ImA9WhZaGEo.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-1505461816681965930</id><published>2010-11-28T16:22:00.005+04:00</published><updated>2011-07-05T19:20:39.965+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-05T19:20:39.965+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="cpan" /><category scheme="http://www.blogger.com/atom/ns#" term="xing" /><title>Upgrading Perl from 5.8.7 to 5.12.2</title><content type="html">One of my recent projects at work (&lt;a href="https://www.xing.com/"&gt;xing.com&lt;/a&gt;) was to upgrade Perl from version 5.8.7 to the most recent one: 5.12.2

My first attempt was to compile a fresh new Perl on the development environment. Once that was done I generated a list of modules, which were installed on the current Perl 5.8.7 using &lt;a href="http://search.cpan.org/dist/ExtUtils-Install/"&gt;ExtUtils::Installed&lt;/a&gt;:

&lt;pre class="sh_sh"&gt;perl -MExtUtils::Installed -e 'printf "%s\n", $_ for ExtUtils::Installed-&gt;new-&gt;modules'
Any::Moose
AnyEvent
App::Cache
AppConfig
Archive::Peek
...
&lt;/pre&gt;

This list could be directly piped into the CPAN shell of the Perl 5.12.2 to install all the modules. A few hours later I could finally test the new Perl installation using our software. Unfortunately the software just crashed, spitting out some error like "Wide character in syswrite" (IIRC). After fixing this error, the next one showed up. It was something about Moose. At this point I was thinking the first time that it might not be a good idea to upgrade Perl &lt;i&gt;and&lt;/i&gt; install the most recent versions of all modules. The modules used on the Perl 5.8.7 installation are quite old. But instead of doing the right thing (installing the same module versions on 5.12.2 as on 5.8.7) I tried the same thing with 5.10. This attempt failed in the same way as the last one did. Next try: 5.8.7. Result: FAIL. Then I was sure that it's no good idea to use the most recent versions of all modules/dependencies. So I had to find a way to not only install a list of modules, but also to install all of them in certain versions. Unfortunately I could not find any solution which was working out-of-the-box. Though I was sure I'm not the first one doing exactly this task.
Basically I just needed an outdated CPAN mirror having all modules in the same version the Perl 5.8.7 installation had. So I used again &lt;a href="http://search.cpan.org/dist/ExtUtils-Install/"&gt;ExtUtils::Installed&lt;/a&gt; to generate a list of modules, this time containing their version:

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl
use strict;
use warnings;
use ExtUtils::Installed;
my $eui = ExtUtils::Installed-&gt;new;
foreach my $m ( $eui-&gt;modules ) {
    printf "%s %s\n", $m, $eui-&gt;version($m);
}
&lt;/pre&gt;
The 2nd step was using &lt;a href="http://search.cpan.org/dist/CPAN-Mini/"&gt;minicpan&lt;/a&gt; to create a local CPAN mirror. Some of the modules of the Perl 5.8.7 installation were so old, that I could not find the tar balls on CPAN anymore. I had to write another script using &lt;a href="http://search.cpan.org/dist/BackPAN-Index/"&gt;BackPAN::Index&lt;/a&gt; to download them from &lt;a href="http://backpan.perl.org/"&gt;BackPAN&lt;/a&gt;:

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl
use strict;
use warnings;
use BackPAN::Index;
use Data::Dumper;
use ExtUtils::Installed;
use File::Basename qw(basename dirname);
use File::Path qw(make_path);

my $bp_base = 'http://backpan.perl.org';

my $backpan = BackPAN::Index-&gt;new;
my $eui     = ExtUtils::Installed-&gt;new;

my @notfound = ();

foreach my $m ( $eui-&gt;modules ) {
    my $v = sprintf '%s', $eui-&gt;version($m);    # force stringify
    my $d = $m;
    $d =~ s/::/-/g;
    my $release = $backpan-&gt;release( $d, $v );
    unless ($release) {
        push @notfound, "$m =&gt; $v";
        next;
    }
    my $filename = basename( $release-&gt;path );
    my $dirname  = dirname( $release-&gt;path );
    unlink $filename if -f $filename;
    make_path($dirname) unless -d $dirname;
    printf "Downloading %s/%s to %s/%s\n", $bp_base, $release-&gt;path, $dirname, $filename;
    system( sprintf 'wget -P %s -q %s/%s', $dirname, $bp_base, $release-&gt;path );
}

if (@notfound) {
    printf "Could not find following distributions:\n%s\nDONE.\n", join "\n", @notfound;
}&lt;/pre&gt;

Once I got all modules, I had to inject them into my local CPAN mirror using &lt;a href="http://search.cpan.org/dist/CPAN-Mini-Inject"&gt;CPAN::Mini::Inject&lt;/a&gt;. Finally I just had to add my CPAN mirror to CPAN::Config of the Perl 5.12.2 installation and could pipe the module list of the Perl 5.8.7 installation directly into the CPAN shell. As a result it installed all modules in the same versions.

Finally our software was working (almost) fine on the new shiny Perl 5.12.2 installation. A few fixes here and a few fixes there and it went live about 4 weeks ago. You can find all of the scripts I used for this upgrade on my &lt;a href="https://github.com/plu/bin"&gt;github account&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-1505461816681965930?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/-I3PeaR129Q" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/1505461816681965930/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2010/11/upgrading-perl-from-587-to-5122.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/1505461816681965930?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/1505461816681965930?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/-I3PeaR129Q/upgrading-perl-from-587-to-5122.html" title="Upgrading Perl from 5.8.7 to 5.12.2" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_XaSgvcru55o/S7x_nXDKitI/AAAAAAAAAAM/EJ3OWA2qa8Q/s1600-R/f3dc44dae.7162667,6.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://www.pqpq.de/2010/11/upgrading-perl-from-587-to-5122.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEcCRHY6fip7ImA9Wx9TGUk.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-2907760524931402219</id><published>2010-11-28T15:50:00.000+04:00</published><updated>2010-11-28T16:07:45.816+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-28T16:07:45.816+04:00</app:edited><title>Outsourcing</title><content type="html">In the past I preferred to host everything myself: eMail, HTTP, git and even DNS. Later I realized that there are providers which do a much better job in hosting this stuff for me. So the first thing I moved was all my eMail stuff to &lt;a href="http://www.google.com/apps/"&gt;Google Apps&lt;/a&gt;. Later I moved all my repositories to &lt;a href="https://github.com/"&gt;github.com&lt;/a&gt;, my blog to &lt;a href="http://www.blogger.com/"&gt;blogger.com&lt;/a&gt; (which is Google as well) and my domains to &lt;a href="http://providerdomain.de/"&gt;providerdomain.de&lt;/a&gt;. After that there were only three things left on my private server: OpenVPN, irssi and my parents webpage incl. admin backend (&lt;a href="http://www.plunien.de"&gt;plunien.de&lt;/a&gt;). OpenVPN could be easily replaced by &lt;a href="http://www.teamviewer.com/"&gt;TeamViewer&lt;/a&gt;. The only use case of the VPN was to support my parents once a year fixing a problem on their macs. Their webpage was implemented in &lt;a href="http://www.catalystframework.org/"&gt;Perl/Catalyst&lt;/a&gt; which I replaced recently with &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; on &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt;: &lt;a href="https://github.com/plu/plunien-de"&gt;https://github.com/plu/plunien-de&lt;/a&gt;. So the last but not least irssi could not be replaced. I don't like any other IRC client, neither I do like to run it on my local mac. But at least I could replace that private server with a much less powerful (and much cheaper) vserver from &lt;a href="https://odac.de/"&gt;odac.de&lt;/a&gt; to run irssi on.

So I'd like to thank every of those providers for doing a great job. Especially Google! It was fun to learn Python/Django/GAE.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-2907760524931402219?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/msVDiG76M68" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/2907760524931402219/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2010/11/outsourcing.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2907760524931402219?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2907760524931402219?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/msVDiG76M68/outsourcing.html" title="Outsourcing" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_XaSgvcru55o/S7x_nXDKitI/AAAAAAAAAAM/EJ3OWA2qa8Q/s1600-R/f3dc44dae.7162667,6.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2010/11/outsourcing.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkENRX49cCp7ImA9Wx9SEEg.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-2919363204507339629</id><published>2010-06-10T10:12:00.000+04:00</published><updated>2010-11-29T21:11:34.068+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-29T21:11:34.068+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="GPW" /><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="Gearman::Driver" /><category scheme="http://www.blogger.com/atom/ns#" term="cpan" /><category scheme="http://www.blogger.com/atom/ns#" term="GPW2010" /><category scheme="http://www.blogger.com/atom/ns#" term="Schorndorf" /><title>Gearman::Driver @ XING (GPW2010)</title><content type="html">Yesterday I gave a talk at the &lt;a href="http://conferences.yapceurope.org/gpw2010/"&gt;German Perl Workshop&lt;/a&gt; in Schorndorf about &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; @ &lt;a href="http://www.xing.com/"&gt;XING&lt;/a&gt;. The &lt;a href="http://dl.dropbox.com/u/1365846/talks/gpw2010/index.html"&gt;slides&lt;/a&gt; can be found &lt;a href="http://dl.dropbox.com/u/1365846/talks/gpw2010/index.html"&gt;here&lt;/a&gt; as well as the &lt;a href="http://dl.dropbox.com/u/1365846/talks/gpw2010/code.tar.gz"&gt;code examples&lt;/a&gt; I've used in the &lt;a href="http://dl.dropbox.com/u/1365846/talks/gpw2010/index.html"&gt;slides&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-2919363204507339629?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/as93kI_K45Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/2919363204507339629/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2010/06/gearmandriver-xing-gpw2010.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2919363204507339629?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2919363204507339629?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/as93kI_K45Y/gearmandriver-xing-gpw2010.html" title="Gearman::Driver @ XING (GPW2010)" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://2.bp.blogspot.com/_XaSgvcru55o/S7x_nXDKitI/AAAAAAAAAAM/EJ3OWA2qa8Q/s1600-R/f3dc44dae.7162667,6.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2010/06/gearmandriver-xing-gpw2010.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4CRXw5fSp7ImA9WhZaGEU.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-7412633010196908549</id><published>2010-01-30T22:42:00.004+04:00</published><updated>2011-07-05T19:49:24.225+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-05T19:49:24.225+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="Gearman" /><title>Gearman::Driver</title><content type="html">Gearman was initially developed by &lt;a href="http://www.danga.com/gearman/"&gt;Danga&lt;/a&gt;. It's a very nice framework to distribute (sometimes long running) tasks across multiple servers. For a more detailed description see &lt;a href="http://gearman.org/"&gt;gearman.org&lt;/a&gt; where you will also find a rewrite of it in C.

First let's take a look at it without &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt;, using a plain &lt;a href="http://search.cpan.org/dist/Gearman-XS/"&gt;Gearman::XS&lt;/a&gt; setup:

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl
use strict;
use warnings;
use Gearman::XS::Worker;
use Gearman::XS qw(:constants);
use Imager;

my $worker = Gearman::XS::Worker-&gt;new;
$worker-&gt;add_server( 'localhost', 4730 );

$worker-&gt;add_function( "convert_to_jpeg", 0, \&amp;convert_to_jpeg, {} );
$worker-&gt;add_function( "convert_to_gif",  0, \&amp;convert_to_gif,  {} );

while (1) {
    my $ret = $worker-&gt;work();
    if ( $ret != GEARMAN_SUCCESS ) {
        printf( STDERR "%s\n", $worker-&gt;error() );
    }
}

sub convert_to_jpeg {
    my ($job) = @_;
    return _convert( $job-&gt;workload, 'jpeg' );
}

sub convert_to_gif {
    my ($job) = @_;
    return _convert( $job-&gt;workload, 'gif' );
}

sub _convert {
    my ( $in_data, $format ) = @_;
    my $img = Imager-&gt;new();
    my $out_data;
    $img-&gt;read( data =&gt; $in_data ) or die;
    $img-&gt;write( data =&gt; \$out_data, type =&gt; $format ) or die;
    return $out_data;
}&lt;/pre&gt;

The problem in this design is to have two functions in the same script. If you depend on running convert_to_jpeg and convert_to_gif at the same time you could split up the script:

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl
use strict;
use warnings;
use Gearman::XS::Worker;
use Gearman::XS qw(:constants);
use Imager;

my $worker = Gearman::XS::Worker-&gt;new;
$worker-&gt;add_server( 'localhost', 4730 );

$worker-&gt;add_function( "convert_to_jpeg", 0, \&amp;convert_to_jpeg, {} );

while (1) {
    my $ret = $worker-&gt;work();
    if ( $ret != GEARMAN_SUCCESS ) {
        printf( STDERR "%s\n", $worker-&gt;error() );
    }
}

sub convert_to_jpeg {
    my ($job) = @_;
    return _convert( $job-&gt;workload, 'jpeg' );
}

sub _convert {
    my ( $in_data, $format ) = @_;
    my $img = Imager-&gt;new();
    my $out_data;
    $img-&gt;read( data =&gt; $in_data ) or die;
    $img-&gt;write( data =&gt; \$out_data, type =&gt; $format ) or die;
    return $out_data;
}&lt;/pre&gt;

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl
use strict;
use warnings;
use Gearman::XS::Worker;
use Gearman::XS qw(:constants);
use Imager;

my $worker = Gearman::XS::Worker-&gt;new;
$worker-&gt;add_server( 'localhost', 4730 );

$worker-&gt;add_function( "convert_to_gif", 0, \&amp;convert_to_gif, {} );

while (1) {
    my $ret = $worker-&gt;work();
    if ( $ret != GEARMAN_SUCCESS ) {
        printf( STDERR "%s\n", $worker-&gt;error() );
    }
}

sub convert_to_gif {
    my ($job) = @_;
    return _convert( $job-&gt;workload, 'gif' );
}

sub _convert {
    my ( $in_data, $format ) = @_;
    my $img = Imager-&gt;new();
    my $out_data;
    $img-&gt;read( data =&gt; $in_data ) or die;
    $img-&gt;write( data =&gt; \$out_data, type =&gt; $format ) or die;
    return $out_data;
}&lt;/pre&gt;

The next problem is about memory: Having many scripts running in separate processes using the same (sometimes heavyweight) library is not very smart. And especially in this example where images are processed those separate processes may consume a lot of ram after a long runtime. So it would be cool to share the memory consumed by libraries as well as freeing up memory from time to time. This is where &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; comes in handy. It's not only sharing memory by using the advantages of &lt;a href="http://en.wikipedia.org/wiki/Copy-on-write"&gt;copy-on-write&lt;/a&gt; but it's also having other cool features like restarting worker processes after some idle time to free up memory. Besides that it got some telnet interface to change amount of processes on runtime as well as getting some statistics et cetera.

So let's re-implement the example above:

&lt;pre class="sh_perl"&gt;package GDExamples::Convert;

use base qw(Gearman::Driver::Worker);
use Moose;
use Imager;

sub process_name {
    my ( $self, $orig, $job_name ) = @_;
    return "$orig ($job_name)";
}

sub convert_to_jpeg : Job : MinProcesses(0) : MaxProcesses(5) {
    my ( $self, $job, $workload ) = @_;
    return _convert( $workload, 'jpeg' );
}

sub convert_to_gif : Job : MinProcesses(0) : MaxProcesses(5) {
    my ( $self, $job, $workload ) = @_;
    return _convert( $workload, 'gif' );
}

sub _convert {
    my ( $in_data, $format ) = @_;
    my $img = Imager-&gt;new();
    my $out_data;
    $img-&gt;read( data =&gt; $in_data ) or die;
    $img-&gt;write( data =&gt; \$out_data, type =&gt; $format ) or die;
    return $out_data;
}&lt;/pre&gt;

To run that worker you can use the script called &lt;strong&gt;gearman_driver.pl&lt;/strong&gt; which is part of the &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; distribution.

&lt;pre class="sh_sourceCode"&gt;usage: gearman_driver.pl [long options...]
--loglevel           Log level (default: INFO)
--lib                Example: --lib ./lib --lib /custom/lib
--max_idle_time      How many seconds a worker may be idle before its killed
--server             Gearman host[:port][,host[:port]]
--logfile            Path to logfile (default: gearman_driver.log)
--console_port       Port of management console (default: 47300)
--interval           Interval in seconds (see Gearman::Driver::Observer)
--loglayout          Log message layout (default: [%d] %p %m%n)
--namespaces         Example: --namespaces My::Workers --namespaces My::OtherWorkers&lt;/pre&gt;

Run the example: &lt;strong&gt;gearman_driver.pl --namespaces GDExamples &amp;amp;&lt;/strong&gt;

You can easily test the workers using the gearman client:
&lt;ul&gt;
&lt;li&gt;gearman -f GDExamples::Convert::convert_to_jpeg &lt;&gt; cpan.jpg&lt;/li&gt;
&lt;li&gt;gearman -f GDExamples::Convert::convert_to_gif &lt;&gt; cpan.gif&lt;/li&gt;
&lt;/ul&gt;

Now let's see what we did. First of all you may have noticed the attributes on both methods:
&lt;ul&gt;
&lt;li&gt;sub convert_to_jpeg : &lt;strong&gt;Job&lt;/strong&gt; : MinProcesses(0) : MaxProcesses(5) {}&lt;/li&gt;
&lt;li&gt;sub convert_to_jpeg : Job : &lt;strong&gt;MinProcesses(0)&lt;/strong&gt; : MaxProcesses(5) {}&lt;/li&gt;
&lt;li&gt;sub convert_to_jpeg : Job : MinProcesses(0) : &lt;strong&gt;MaxProcesses(5)&lt;/strong&gt; {}&lt;/li&gt;
&lt;/ul&gt;

Basically you only need to add the attribute &lt;strong&gt;Job&lt;/strong&gt; to the methods which should be registered with gearmand. All other attributes are optional. Your method will be registered with the full package name:
&lt;ul&gt;
&lt;li&gt;GDExamples::Convert::convert_to_jpeg&lt;/li&gt;
&lt;li&gt;GDExamples::Convert::convert_to_gif&lt;/li&gt;
&lt;/ul&gt;

If you don't like that behaviour you can override &lt;strong&gt;prefix&lt;/strong&gt;. By default it's implemented this way:

&lt;pre class="sh_perl"&gt;sub prefix {
    return ref(shift) . '::';
}&lt;/pre&gt;

There are other predefined methods which can be overridden, like &lt;strong&gt;begin&lt;/strong&gt; or &lt;strong&gt;end&lt;/strong&gt;. Both are called whenever a job function is called, &lt;strong&gt;begin&lt;/strong&gt; before the job method is called and &lt;strong&gt;end&lt;/strong&gt; afterwards (even if the job method died).

The other two attributes tell &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; how many processes should be forked and work on that function/job. If you set &lt;strong&gt;MinProcesses&lt;/strong&gt; to 0 no process is preforked at all. It's being forked on demand: &lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; monitors the &lt;a href="http://search.cpan.org/dist/Net-Telnet-Gearman/"&gt;telnet interface&lt;/a&gt; of gearmand for queued jobs to see if it's necessary to fork a new process. If there are many jobs in the queue it will fork even more processes until &lt;strong&gt;MaxProcesses&lt;/strong&gt; is reached. After all jobs are done it will kill all processes again. The default behaviour is to do that immediately, but can be changed by setting &lt;strong&gt;max_idle_time&lt;/strong&gt; to 300 (seconds) for example.

&lt;a href="http://search.cpan.org/dist/Gearman-Driver/"&gt;Gearman::Driver&lt;/a&gt; itself got a &lt;a href="http://search.cpan.org/dist/Gearman-Driver/lib/Gearman/Driver/Console.pm"&gt;telnet interface&lt;/a&gt; as well. There's also a client coming with the distribution having readline support. Another feature is to connect to multiple servers at once to send the same commands to all servers. Really handy in a big environment.

&lt;pre class="sh_sourceCode"&gt;gearman_driver_console.pl --server localhost:47300
console&gt; status
localhost:47300&gt; GDExamples::Convert::convert_to_gif   0  5  0  2010-01-30T19:45:43  1970-01-01T00:00:00
localhost:47300&gt; GDExamples::Convert::convert_to_jpeg  0  5  0  2010-01-30T19:45:53  1970-01-01T00:00:00
localhost:47300&gt; .
console&gt; set_processes GDExamples::Convert::convert_to_gif 2 10
localhost:47300&gt; OK
localhost:47300&gt; .
console&gt; status
localhost:47300&gt; GDExamples::Convert::convert_to_gif   2  10  2  2010-01-30T19:45:43  1970-01-01T00:00:00
localhost:47300&gt; GDExamples::Convert::convert_to_jpeg  0   5  0  2010-01-30T19:45:53  1970-01-01T00:00:00
localhost:47300&gt; .&lt;/pre&gt;

I've just shipped v0.01017 to the &lt;a href="http://www.cpan.org/"&gt;CPAN&lt;/a&gt; containing the examples in this blog post. If you can't wait for it, you can fetch it from &lt;a href="http://github.com/plu/gearman-driver"&gt;GitHub&lt;/a&gt;. Any comments/suggestions/rants will be highly appreciated. Thanks.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-7412633010196908549?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/SezrVnQ2RDc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/7412633010196908549/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2010/01/gearmandriver.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/7412633010196908549?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/7412633010196908549?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/SezrVnQ2RDc/gearmandriver.html" title="Gearman::Driver" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2010/01/gearmandriver.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0UCSHw9cCp7ImA9WhZaGEU.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-7515179558737801368</id><published>2009-05-28T00:39:00.001+04:00</published><updated>2011-07-05T19:54:29.268+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-05T19:54:29.268+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="DBIx::Class" /><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="DBIC" /><title>Catalyst + DBIC + DAO</title><content type="html">Recently there was a &lt;a href="http://www.mail-archive.com/catalyst@lists.scsys.co.uk/msg06932.html"&gt;question&lt;/a&gt; on the &lt;a href="http://www.catalystframework.org/"&gt;Catalyst&lt;/a&gt; &lt;a href="http://dev.catalystframework.org/wiki/mailinglists"&gt;mailing list&lt;/a&gt; which i've been asked by some teammate too: "How do i create DataAccessObjects with methods i can use in Catalyst as well as in CronJobs or other scripts?"

You're too lazy to read the full entry, you just want to see the code? It's available on &lt;a href="http://www.github.com/"&gt;GitHub&lt;/a&gt;: &lt;a href="http://github.com/plu/dao-example/tree/master"&gt;http://github.com/plu/dao-example/tree/master&lt;/a&gt;

It's quite easy, all you need is custom &lt;a href="http://search.cpan.org/dist/DBIX-Class"&gt;DBIx::Class&lt;/a&gt; ResultSets. I guess you've already setup a &lt;a href="http://search.cpan.org/dist/Catalyst-Model-DBIC-Schema"&gt;model&lt;/a&gt; in your Catalyst app:

&lt;pre class="sh_perl"&gt;package DAO::Example::Model::DB;

use strict;
use warnings;
use base 'Catalyst::Model::DBIC::Schema';

1;&lt;/pre&gt;

...and a config file that might look like:

&lt;pre class="sh_sourceCode"&gt;# rename this file to DAO::Example.yml and put a : in front of "name" if
# you want to use yaml like in old versions of Catalyst
name DAO::Example

&lt;model::db&gt;
    schema_class    DAO::Example::DB
    connect_info    dbi:SQLite:dao_example.db
    connect_info    username
    connect_info    password
&lt;/model::db&gt;&lt;/pre&gt;

To get custom resultsets for all of your result classes setup the schema using load_namespaces:

&lt;pre class="sh_perl"&gt;package DAO::Example::DB;

use strict;
use warnings;
use base qw/DBIx::Class::Schema/;

__PACKAGE__-&gt;load_namespaces( default_resultset_class =&gt; '+DAO::Example::DB::Base::ResultSet' );

1;&lt;/pre&gt;

Going that way your result classes will be expected in DAO::Example::DB::Result:: namespace, resultsets in DAO::Example::DB::ResultSet::. If you create a class DAO::Example::DB::Result::Person DBIC will look if DAO::Example::DB::ResultSet::Person exists and inherit all person resultsets from that class. If there's no such class the resultset will be inherited from DAO::Example::DB::Base::ResultSet.

Let's add some methods to the person resultset:

&lt;pre class="sh_perl"&gt;package DAO::Example::DB::ResultSet::Person;

use strict;
use warnings;
use base qw/DAO::Example::DB::Base::ResultSet/;

sub by_username {
    my ( $rs, $username ) = @_;
    return $rs-&gt;search( { 'me.username' =&gt; $username }, { key =&gt; 'unique_username' } );
}

sub prefetch_all {
    my ($rs) = @_;
    return $rs-&gt;search( {}, { prefetch =&gt; [ { personroles =&gt; [qw/role/] } ] } );
}

1;&lt;/pre&gt;

And to the default resultset:

&lt;pre class="sh_perl"&gt;package DAO::Example::DB::Base::ResultSet;

use strict;
use warnings;
use base qw/DBIx::Class::ResultSet::HashRef DAO::Example::DB::Base::Any/;

sub active {
    my ($rs) = @_;
    return $rs-&gt;search( { 'me.active' =&gt; 1 } );
}

sub inactive {
    my ($rs) = @_;
    return $rs-&gt;search( { 'me.active' =&gt; { '!=' =&gt; 1 } } );
}

1;&lt;/pre&gt;

In catalyst you would call these methods that way:

&lt;pre class="sh_perl"&gt;package DAO::Example::Controller::Root;

use strict;
use warnings;
use parent 'Catalyst::Controller';
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;

__PACKAGE__-&gt;config-&gt;{namespace} = '';

sub index : Path : Args(0) {
    my ( $self, $c ) = @_;

    unless ( -e $c-&gt;path_to('dao_example.db') ) {
        $c-&gt;model('DB')-&gt;schema-&gt;deploy;
        $c-&gt;model('DB')-&gt;schema-&gt;init;
    }

    $c-&gt;res-&gt;print('&lt;pre&gt;');
    $c-&gt;res-&gt;print( Dumper $c-&gt;config );
    $c-&gt;res-&gt;print( Dumper $c-&gt;model('DB')-&gt;resultset('Person')-&gt;active-&gt;hashref_array );
    $c-&gt;res-&gt;print( Dumper $c-&gt;model('DB')-&gt;resultset('Person')-&gt;inactive-&gt;hashref_array );
    $c-&gt;res-&gt;print( Dumper $c-&gt;model('DB')-&gt;resultset('Person')-&gt;by_username('plu')-&gt;prefetch_all-&gt;hashref_array );
    $c-&gt;res-&gt;print('&lt;/pre&gt;');
}&lt;/pre&gt;

The method &lt;strong&gt;hashref_array&lt;/strong&gt; comes from &lt;a href="http://search.cpan.org/dist/DBIx-Class-ResultSet-HashRef"&gt;DBIx::Class::ResultSet::HashRef&lt;/a&gt;. How would you call that from a CronJob / without Catalyst?

&lt;pre class="sh_perl"&gt;#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use DAO::Example::Utils qw/schema config/;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;

unless ( -e "$FindBin::Bin/../dao_example.db" ) {
    schema-&gt;deploy;
    schema-&gt;init;
}

print Dumper config;

print Dumper schema-&gt;resultset('Person')-&gt;active-&gt;hashref_array;

print Dumper schema-&gt;resultset('Person')-&gt;inactive-&gt;hashref_array;

print Dumper schema-&gt;resultset('Person')-&gt;by_username('plu')-&gt;prefetch_all-&gt;hashref_array;&lt;/pre&gt;

The "magic" thing is to instantiate a DBIx::Class::Schema object using your Catalyst config file. I tend to write a small utility class to achieve that:

&lt;pre class="sh_perl"&gt;package DAO::Example::Utils;

use strict;
use warnings;
use base 'Exporter';
use Config::JFDI;
use DAO::Example::DB;

use vars qw/@EXPORT_OK $schema $config/;

@EXPORT_OK = qw/
  schema
  config
  /;

sub config {
    return $config if defined $config;
    $config = Config::JFDI-&gt;new( name =&gt; "DAO::Example" )-&gt;get;
    return $config;
}

sub schema {
    return $schema if defined $schema;
    $schema = DAO::Example::DB-&gt;connect( @{ config-&gt;{'Model::DB'}{connect_info} || [] } );
    return $schema;
}

1;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-7515179558737801368?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/b_zGu_7l6wU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/7515179558737801368/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2009/05/catalyst-dbic-dao.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/7515179558737801368?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/7515179558737801368?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/b_zGu_7l6wU/catalyst-dbic-dao.html" title="Catalyst + DBIC + DAO" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2009/05/catalyst-dbic-dao.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0QAQ309fip7ImA9WhZaGEU.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-5249548867991234258</id><published>2009-02-01T21:44:00.001+04:00</published><updated>2011-07-05T19:55:42.366+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-05T19:55:42.366+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="POE" /><category scheme="http://www.blogger.com/atom/ns#" term="irssi" /><category scheme="http://www.blogger.com/atom/ns#" term="Twitter" /><title>irssi: Twitter</title><content type="html">&lt;ul&gt;
&lt;li&gt;install all dependencies (POE, POE::Loop::Glib, POE::Session::Irssi, Net::Twitter, HTML::Entities, HTTP::Date)&lt;/li&gt;
&lt;li&gt;change credentials&lt;/li&gt;
&lt;li&gt;/script load /path/to/twitter.pl&lt;/li&gt;
&lt;li&gt;/window new hidden&lt;/li&gt;
&lt;li&gt;change to the new window and type /window name twitter&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="sh_perl"&gt;use strict;
use warnings;
use Irssi;
use Glib;
use POE qw(Loop::Glib Session::Irssi);
use Net::Twitter;
use HTML::Entities qw( decode_entities );
use HTTP::Date qw( time2str );

my $VERSION = '0.1';
my %IRSSI   = (
    authors =&gt; 'Johannes Plunien',
    contact =&gt; 'http://www.pqpq.de/contact/',
    name    =&gt; 'Twitter',
    license =&gt; 'Perl',
);

my %twitter_config = (
    username        =&gt; 'plutooth',
    password        =&gt; 'secret',
    update_interval =&gt; 90,           # seconds
);

POE::Session::Irssi-&gt;create(
    irssi_commands =&gt; {
        tmsg =&gt; sub {
            $_[HEAP]-&gt;{twitter}-&gt;update( join " ", ( @{ $_[ARG1] } )[0] );
            $_[KERNEL]-&gt;delay( update =&gt; 5 );
        },
    },
    inline_states =&gt; {
        _start =&gt; sub {
            my $heap = $_[HEAP];
            $heap-&gt;{window} = Irssi::window_find_name('twitter');
            Irssi::print("Create a window named 'twitter'") if !$heap-&gt;{window};
            my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
            $heap-&gt;{interval} = delete $twitter_config{update_interval};
            $heap-&gt;{twitter}  = Net::Twitter-&gt;new(%twitter_config)
              or Irssi::print("Twitter ERROR: $!");
            $kernel-&gt;delay( update =&gt; 2 );
        },
        update =&gt; sub {
            my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
            my $params = {};
            $params-&gt;{since} = $heap-&gt;{last_update} if defined $heap-&gt;{last_update};
            my $timeline    = $heap-&gt;{twitter}-&gt;friends_timeline($params);
            my $http_status = $heap-&gt;{twitter}-&gt;http_code;
            $heap-&gt;{window} = Irssi::window_find_name('twitter');
            if (   $http_status == 200
                &amp;&amp; $heap-&gt;{window} )
            {
                $heap-&gt;{last_update} = time2str(time);
                $kernel-&gt;yield( display =&gt; $timeline ) if scalar @$timeline &gt; 0;
            }
            $kernel-&gt;delay( update =&gt; $heap-&gt;{interval} );
        },
        display =&gt; sub {
            my ( $kernel, $heap, $timeline ) = @_[ KERNEL, HEAP, ARG0 ];
            foreach my $row ( reverse @$timeline ) {
                my $screen_name = $row-&gt;{user}{screen_name};
                my $text        = $row-&gt;{text};
                $text =~ s/\n/\n   /;
                my $cleantext = decode_entities($text);
                my $message   = "&lt;$screen_name&gt; $cleantext";
                $heap-&gt;{window}-&gt;print( $message, MSGLEVEL_PUBLIC );
            }
        },
    },
);&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-5249548867991234258?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/SRsvnyImBjw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/5249548867991234258/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2009/02/irssi-twitter.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/5249548867991234258?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/5249548867991234258?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/SRsvnyImBjw/irssi-twitter.html" title="irssi: Twitter" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2009/02/irssi-twitter.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0MERXY-eSp7ImA9WhZaGEU.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-2468241276196862242</id><published>2008-10-02T12:01:00.001+04:00</published><updated>2011-07-05T19:56:44.851+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-05T19:56:44.851+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="irssi" /><title>irssi: Close all queries</title><content type="html">This irssi script creates a new command to close all open queries at once. I don't know what else to explain here, i think the script explains itself.

&lt;pre class="sh_perl"&gt;use strict;
use warnings;
use Irssi;
use Glib;
use POE qw(Loop::Glib Session::Irssi);

my $VERSION = '0.1';
my %IRSSI   = (
    authors =&gt; 'Johannes Plunien',
    contact =&gt; 'http://www.pqpq.de/contact/',
    license =&gt; 'Perl',
);

POE::Session::Irssi-&gt;create(
    irssi_commands =&gt; {
        qc =&gt; sub {
            foreach my $w ( Irssi::windows() ) {
                my $act = $w-&gt;{active};
                next unless defined $act-&gt;{type};
                $w-&gt;command("window close") if $act-&gt;{type} eq 'QUERY';
            }
        },
    },
);&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-2468241276196862242?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/6UCTJ6oGRkI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/2468241276196862242/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2008/10/irssi-close-all-queries.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2468241276196862242?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2468241276196862242?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/6UCTJ6oGRkI/irssi-close-all-queries.html" title="irssi: Close all queries" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2008/10/irssi-close-all-queries.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0MNRXk8fyp7ImA9WhZaGEU.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-3129471095401351225</id><published>2008-07-29T11:12:00.001+04:00</published><updated>2011-07-05T19:58:14.777+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-05T19:58:14.777+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="Firebug" /><title>Firebug debugging fallback</title><content type="html">&lt;a href="https://addons.mozilla.org/de/firefox/addon/1843"&gt;Firebug&lt;/a&gt; is the best tool for debugging heavyweight javascript web applications. You can put &lt;strong&gt;console.log(arguments)&lt;/strong&gt; here and &lt;strong&gt;console.debug(foo)&lt;/strong&gt; there, but ... YOU SHOULD NEVER FORGET TO REMOVE IT! If you forget it, your application stops working in any browser that doesn't know those methods. To avoid this, just include this snippet:

&lt;pre class="sh_javascript"&gt;if(!window.console || !window.console.firebug) {
  window.console = {
    debug: function(){},
    log: function(){}
  };
}&lt;/pre&gt;

/Edit: I just found &lt;a href="http://getfirebug.com/firebug/firebugx.js"&gt;http://getfirebug.com/firebug/firebugx.js&lt;/a&gt; which is more complete:

&lt;pre class="sh_javascript"&gt;if (!window.console || !console.firebug)
{
    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];

    window.console = {};
    for (var i = 0; i &lt; names.length; ++i)
        window.console[names[i]] = function() {}
}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-3129471095401351225?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/8MbjKP8GYVw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/3129471095401351225/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2008/07/firebug-debugging-fallback.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/3129471095401351225?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/3129471095401351225?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/8MbjKP8GYVw/firebug-debugging-fallback.html" title="Firebug debugging fallback" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2008/07/firebug-debugging-fallback.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0EGQ3Yyfyp7ImA9WhZaGEU.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-2523139879215863833</id><published>2008-07-26T15:17:00.001+04:00</published><updated>2011-07-05T20:00:22.897+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-05T20:00:22.897+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><category scheme="http://www.blogger.com/atom/ns#" term="ExtJS" /><title>Remote form validation</title><content type="html">&lt;a href="http://catalyst.perl.org/"&gt;Catalyst&lt;/a&gt; and &lt;a href="http://extjs.com/"&gt;ExtJS&lt;/a&gt; are my favourite frameworks for developing web applications. &lt;a href="http://extjs.com/"&gt;ExtJS&lt;/a&gt; provides a lot features to validate form data before the form is being submitted to the server. Everybody knows that you never should trust those values even if it was validated in the browser by &lt;a href="http://extjs.com/"&gt;ExtJS&lt;/a&gt;. Here is a comfortable way to validate form values remotely using &lt;a href="http://search.cpan.org/dist/Catalyst-Plugin-FormValidator-Simple/"&gt;Catalyst::Plugin::FormValidator::Simple&lt;/a&gt;.

The full example can be found on &lt;a href="http://github.com/plu/rfv-example/tree/master"&gt;github&lt;/a&gt;:
&lt;strong&gt;git clone git://github.com/plu/rfv-example.git&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Create an &lt;a href="http://extjs.com/"&gt;ExtJS&lt;/a&gt; FormPanel:
&lt;pre class="sh_javascript"&gt;var win = new Ext.Window({
    .....
    ,items:[
        new Ext.FormPanel({
            ,url:'[% Catalyst.uri_for('/save') %]'
            ,id: 'fp'
            .....
            ,items: [{
                fieldLabel: 'First name',
                name: 'first',
                anchor:'100%'
            },{
                fieldLabel: 'Last name',
                name: 'last',
                anchor:'100%'
            },{
                fieldLabel: 'Company',
                name: 'company',
                anchor:'100%'
            },{
                fieldLabel: 'eMail',
                name: 'email',
                anchor:'100%'
            }]
            ,buttons: [{
                text: 'Save',
                handler: function() {
                    win.findById('fp').getForm().submit();
                }
            }]
        })
    ]
});&lt;/pre&gt;
&lt;/li&gt;

&lt;li&gt;Read validation results and modify form fields:
&lt;pre class="sh_javascript"&gt;var fp = win.findById('fp');
fp.on('actioncomplete', function(a, fp) {
    var r = fp.result;
    for (var field in r.error) {
        fp.form.findField(field).markInvalid(
            r.error[field].join("&amp;lt;br/&amp;gt;")
        );
    }
});&lt;/pre&gt;
&lt;/li&gt;

&lt;li&gt;Validate form values:

&lt;pre class="sh_perl"&gt;sub save : Global {
    my ( $self, $c ) = @_;

    $c-&gt;form(
        first   =&gt; [ qw/NOT_BLANK ASCII/ =&gt; [qw/LENGTH 2 20/] ],
        last    =&gt; [ qw/NOT_BLANK ASCII/ =&gt; [qw/LENGTH 2 20/] ],
        company =&gt; [ qw/NOT_BLANK ASCII/ =&gt; [qw/LENGTH 2 20/] ],
        email   =&gt; [qw/NOT_BLANK EMAIL_LOOSE/],
    );

    my $json : Stashed = { error =&gt; $c-&gt;form-&gt;field_messages('save'), };
}&lt;/pre&gt;

&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-2523139879215863833?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/mcgI89W41fI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/2523139879215863833/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2008/07/remote-form-validation.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2523139879215863833?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/2523139879215863833?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/mcgI89W41fI/remote-form-validation.html" title="Remote form validation" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2008/07/remote-form-validation.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C04BQXg7eCp7ImA9WhZaGEU.&quot;"><id>tag:blogger.com,1999:blog-4934018258162745792.post-965929689729547857</id><published>2008-07-17T10:38:00.002+04:00</published><updated>2011-07-05T20:05:50.600+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-05T20:05:50.600+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MojoMojo" /><category scheme="http://www.blogger.com/atom/ns#" term="Perl" /><title>MojoMojo - make it private</title><content type="html">&lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt; is a wiki written in &lt;a href="http://catalyst.perl.org/"&gt;Catalyst&lt;/a&gt; and &lt;a href="http://search.cpan.org/dist/DBIx-Class"&gt;DBIx::Class&lt;/a&gt;. It was the best choice to replace my private &lt;a href="http://www.mediawiki.org/wiki/MediaWiki/de"&gt;MediaWiki&lt;/a&gt;. The installation is straight forward:

&lt;pre class="sh_sourceCode"&gt;svn co http://code2.0beta.co.uk/mojomojo/svn/trunk/ MojoMojo
cd MojoMojo
perl Makefile.PL
make&lt;/pre&gt;

The standard way to install MojoMojo is through CPAN. You can find the distribution for it &lt;a href="http://search.cpan.org/dist/MojoMojo/"&gt;here&lt;/a&gt;, or you can install it from the command line like this:
&lt;pre class="brush: perl"&gt;sudo cpan MojoMojo&lt;/pre&gt;

After successful installation &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt; needs to be configured using its mojomojo.conf. Generally you can just edit this file directly, but if you're using the svn checkout and probably want to contribute some changes later you better go for a new mojomojo_local.conf. So you can have your database credentials in that file and you don't have to worry about committing it by accident.

&lt;pre class="sh_sourceCode"&gt;&amp;lt;Model::DBIC&amp;gt;
    connect_info   dbi:mysql:mojomojo
    connect_info   mojomojo
    connect_info   sTrOnGpAsSwOrD
&amp;lt;/Model::DBIC&amp;gt;&lt;/pre&gt;

Create database tables:

&lt;pre&gt;./script/mojomojo_spawn_db.pl&lt;/pre&gt;

Done. Now you can deploy &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt; using &lt;a href="http://perl.apache.org/"&gt;mod_perl&lt;/a&gt;, &lt;a href="http://www.fastcgi.com/"&gt;FastCGI&lt;/a&gt; or just run it using Catalysts development webserver:

&lt;pre&gt;./script/mojomojo_server -p 3000&lt;/pre&gt;

My previous &lt;a href="http://www.mediawiki.org/wiki/MediaWiki/de"&gt;MediaWiki&lt;/a&gt; installation was only accessible by username and password. So i needed to get my head around the authorization implementation in &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt;. First of all you need to add some lines to the config:

&lt;pre class="sh_sourceCode"&gt;&amp;lt;permissions&amp;gt;
check_permission_on_view    1
cache_permission_data       1
create_allowed              0
delete_allowed              0
edit_allowed                0
view_allowed                0
attachment_allowed          0
&amp;lt;/permissions&amp;gt;&lt;/pre&gt;

Additionally there are a few database changes necessary:

&lt;pre class="sh_sourceCode"&gt;-- add a new role
insert into role values (null, 'user', 1);

-- add this role to the standard admin user
insert into role_member values (1, 2, 1);

-- allow everything to that new role
insert into path_permissions values
('/', 1, 'no', 'yes', 'yes', 'yes', 'yes', 'yes');
insert into path_permissions values
('/', 1, 'yes', 'yes', 'yes', 'yes', 'yes', 'yes');&lt;/pre&gt;

How does it look like now?

&lt;pre class="sh_sourceCode"&gt;mysql&gt; select * from role;
+----+------+--------+
| id | name | active |
+----+------+--------+
|  1 | user |      1 |
+----+------+--------+

mysql&amp;gt; select * from role_member;
+------+--------+-------+
| role | person | admin |
+------+--------+-------+
|    1 |      2 |     1 |
+------+--------+-------+

mysql&gt; select * from path_permissions\G
*************************** 1. row ***************************
path: /
role: 1
apply_to_subpages: no
create_allowed: yes
delete_allowed: yes
edit_allowed: yes
view_allowed: yes
attachment_allowed: yes
*************************** 2. row ***************************
path: /
role: 1
apply_to_subpages: yes
create_allowed: yes
delete_allowed: yes
edit_allowed: yes
view_allowed: yes
attachment_allowed: yes&lt;/pre&gt;

To get this working please make sure to use at least version 0.999018 or revision 927 of &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt;.

In next release of &lt;a href="http://www.mojomojo.org/"&gt;MojoMojo&lt;/a&gt; there will be also a new flag to enforce login:

&lt;pre class="sh_sourceCode"&gt;&amp;lt;permissions&amp;gt;
    enforce_login 1
&amp;lt;/permissions&amp;gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4934018258162745792-965929689729547857?l=www.pqpq.de' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/plu/~4/cQywKW0Xn48" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.pqpq.de/feeds/965929689729547857/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.pqpq.de/2008/07/mojomojo-make-it-private.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/965929689729547857?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4934018258162745792/posts/default/965929689729547857?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/plu/~3/cQywKW0Xn48/mojomojo-make-it-private.html" title="MojoMojo - make it private" /><author><name>Johannes Plunien</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.pqpq.de/2008/07/mojomojo-make-it-private.html</feedburner:origLink></entry></feed>

