|
NAMENet::Curl::examples - sample modules and test code for Net::CurlCurl::TransportExtracted from "examples/01-curl-transport.pl"This module shows:
Motivationrecv() and send() methods use non-blocking transfer, this may be very annoying in simple scripts. This wrapper implements blocking send() wrapper, and two recv() wrappers called read() and readline().MODULE CODEpackage Curl::Transport; use strict; use warnings; use Net::Curl::Easy qw(/^CURLE_/); use base qw(Net::Curl::Easy); BEGIN { if ( Net::Curl::LIBCURL_VERSION_NUM() < 0x071202 ) { my $ver = Net::Curl::LIBCURL_VERSION(); die "curl $ver does not support send() and recv()"; } # alternatively you can write: if ( not Net::Curl::Easy->can( "send" ) or not Net::Curl::Easy->can( "recv" ) ) { die "Net::Curl is missing send() and recv()\n" } } use constant { B_URI => 0, B_SOCKET => 1, B_VEC => 2, B_READBUF => 3, }; # new( URL ) -- get new object sub new { my $class = shift; my $uri = shift; # use an array as our object base my $base = [ $uri, undef, undef, '' ]; my $self = $class->SUPER::new( $base ); $self->setopt( Net::Curl::Easy::CURLOPT_URL, $uri ); $self->setopt( Net::Curl::Easy::CURLOPT_CONNECT_ONLY, 1 ); # will die if fails $self->perform(); $self->[ B_SOCKET ] = $self->getinfo( Net::Curl::Easy::CURLINFO_LASTSOCKET ); # prepare select vector my $vec = ''; vec( $vec, $self->[ B_SOCKET ], 1 ) = 1; $self->[ B_VEC ] = $vec; return $self; } # send( DATA ) -- send some data, wait for socket availability # if it cannot be sent all at once sub send($$) { my $self = shift; my $data = shift; while ( length $data ) { # copy, because select overwrites those values my $w = $self->[ B_VEC ]; # wait for write select undef, $w, undef, 0; # make sure some write bit is set next unless vec( $w, $self->[ B_SOCKET ], 1 ); # actually send the data my $sent = $self->SUPER::send( $data ); # remove from buffer what we sent substr $data, 0, $sent, ''; }; } # read( SIZE ) -- read SIZE bytes, wait for more data if there # wasn't enough sub read($$) { my $self = shift; my $size = shift; return '' unless $size > 0; while ( length $self->[ B_READBUF ] < $size ) { my $r = $self->[ B_VEC ]; # wait for data select $r, undef, undef, 0; # make sure some read bit is set redo unless vec( $r, $self->[ B_SOCKET ], 1 ); eval { my $l = $self->SUPER::recv( $self->[ B_READBUF ], $size - length $self->[ B_READBUF ] ); }; if ( $@ ) { if ( $@ == CURLE_UNSUPPORTED_PROTOCOL ) { my $uri = $self->[ B_URI ]; warn "Connection to $uri closed: $@\n"; last; } elsif ( $@ == CURLE_AGAIN ) { warn "nothing to read, this should not happen"; } else { die $@; } } } return substr $self->[ B_READBUF ], 0, $size, ''; } # readline() -- read until $/ sub readline($) { my $self = shift; # we allow changing $/, but we don't support $/ = undef. local $/; $/ = "\n" unless defined $/; my $idx; until ( ( $idx = index $self->[ B_READBUF ], $/ ) >= 0 ) { my $r = $self->[ B_VEC ]; # wait for data select $r, undef, undef, 0; # make sure some read bit is set next unless vec( $r, $self->[ B_SOCKET ], 1 ); # read 256 bytes, should be enough in most cases eval { $self->SUPER::recv( $self->[ B_READBUF ], 256 ); }; if ( $@ ) { if ( $@ == CURLE_UNSUPPORTED_PROTOCOL ) { my $uri = $self->[ B_URI ]; warn "Connection to $uri closed: $@\n"; last; } elsif ( $@ == CURLE_AGAIN ) { warn "nothing to read, this should not happen"; } else { die $@; } } } return substr $self->[ B_READBUF ], 0, ($idx + length $/), ''; } 1; TEST APPLICATIONSample application using this module could look like this:#!perl use strict; use warnings; use Curl::Transport; my $host = shift @ARGV || "example.com"; my $t = Curl::Transport->new( "http://$host" ); $t->send( "GET / HTTP/1.0\r\n" ); $t->send( "User-Agent: Curl::Transport test\r\n" ); $t->send( "Accept: */*\r\n" ); $t->send( "Host: $host\r\n" ); $t->send( "Connection: Close\r\n" ); $t->send( "\r\n" ); my $length; { local $/ = "\r\n"; local $_; do { $_ = $t->readline(); $length = 0 | $1 if /Content-Length:\s*(\d+)/; chomp; print "HEADER: $_\n"; } while ( length $_ ); } if ( defined $length ) { print "Reading $length bytes of data:\n"; print $t->read( $length ); print "\nTrying to read one more byte, should fail:\n"; print $t->read( 1 ); print "\n"; } else { print "Don't know how much to read\n"; while ( $_ = $t->readline() ) { print; } } printf "Last error: %s\n", $t->error(); Multi::SimpleExtracted from "examples/02-multi-simple.pl"This module shows how to use Net::Curl::Multi interface correctly in its simpliest form. Uses perl builtin select(). A more advanced code would use callbacks and some event library instead. MotivationWriting a proper multi wrapper code requires a rather good understainding of libcurl multi interface. This code provides a recipie for those who just need something that "simply works".MODULE CODEpackage Multi::Simple; use strict; use warnings; use Net::Curl::Multi; use base qw(Net::Curl::Multi); # make new object, preset the data sub new { my $class = shift; my $active = 0; return $class->SUPER::new( \$active ); } # add one handle and count it sub add_handle($$) { my $self = shift; my $easy = shift; $$self++; $self->SUPER::add_handle( $easy ); } # perform until some handle finishes, does all the magic needed # to make it efficient (check as soon as there is some data) # without overusing the cpu. sub get_one($) { my $self = shift; if ( my @result = $self->info_read() ) { $self->remove_handle( $result[ 1 ] ); return @result; } while ( $$self ) { my $t = $self->timeout; if ( $t != 0 ) { $t = 10000 if $t < 0; my ( $r, $w, $e ) = $self->fdset; select $r, $w, $e, $t / 1000; } my $ret = $self->perform(); if ( $$self != $ret ) { $$self = $ret; if ( my @result = $self->info_read() ) { $self->remove_handle( $result[ 1 ] ); return @result; } } }; return (); } 1; TEST APPLICATIONSample application using this module looks like this:#!perl use strict; use warnings; use Multi::Simple; use Net::Curl::Share qw(:constants); sub easy { my $uri = shift; my $share = shift; require Net::Curl::Easy; my $easy = Net::Curl::Easy->new( { uri => $uri, body => '' } ); $easy->setopt( Net::Curl::Easy::CURLOPT_VERBOSE(), 1 ); $easy->setopt( Net::Curl::Easy::CURLOPT_URL(), $uri ); $easy->setopt( Net::Curl::Easy::CURLOPT_WRITEHEADER(), \$easy->{headers} ); $easy->setopt( Net::Curl::Easy::CURLOPT_FILE(), \$easy->{body} ); $easy->setopt( Net::Curl::Easy::CURLOPT_SHARE(), $share ); # This wasn't needed prior to curl 7.67, which changed the interface # so that an easy that uses a cookie-share now requires an explicit # cookie-engine enable to use cookies. Previously the easy's use of # a cookie-share implicitly enabled the easy's cookie engine. $easy->setopt( Net::Curl::Easy::CURLOPT_COOKIEFILE(), q<> ); return $easy; } my $multi = Multi::Simple->new(); my @uri = ( "http://www.google.com/search?q=perl", "http://www.google.com/search?q=curl", "http://www.google.com/search?q=perl+curl", ); { # share cookies between all handles my $share = Net::Curl::Share->new(); $share->setopt( CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE ); $multi->add_handle( easy( shift ( @uri ), $share ) ); } my $ret = 0; while ( my ( $msg, $easy, $result ) = $multi->get_one() ) { print "\nFinished downloading $easy->{uri}: $result:\n"; printf "Body is %d bytes long\n", length $easy->{body}; print "=" x 80 . "\n"; $ret = 1 if $result; $multi->add_handle( easy( shift ( @uri ), $easy->share ) ) if @uri; } exit $ret; Multi::EventExtracted from "examples/03-multi-event.pl"This module shows how to use Net::Curl::Multi interface with an event library, AnyEvent in this case. MotivationThis is the most efficient method for using Net::Curl::Multi interface, but it requires a really good understanding of it. This code tries to show the quirks found when using event-based programming.MODULE CODEpackage Multi::Event; use strict; use warnings; use AnyEvent; use Net::Curl::Multi qw(/^CURL_POLL_/ /^CURL_CSELECT_/); use base qw(Net::Curl::Multi); BEGIN { if ( not Net::Curl::Multi->can( 'CURLMOPT_TIMERFUNCTION' ) ) { die "Net::Curl::Multi is missing timer callback,\n" . "rebuild Net::Curl with libcurl 7.16.0 or newer\n"; } } sub new { my $class = shift; # no base object this time # we'll use the default hash my $multi = $class->SUPER::new(); $multi->setopt( Net::Curl::Multi::CURLMOPT_SOCKETFUNCTION, \&_cb_socket ); $multi->setopt( Net::Curl::Multi::CURLMOPT_TIMERFUNCTION, \&_cb_timer ); $multi->{active} = -1; return $multi; } # socket callback: will be called by curl any time events on some # socket must be updated sub _cb_socket { my ( $multi, $easy, $socket, $poll ) = @_; #warn "on_socket( $socket => $poll )\n"; # Right now $socket belongs to that $easy, but it can be # shared with another easy handle if server supports persistent # connections. # This is why we register socket events inside multi object # and not $easy. # deregister old io events delete $multi->{ "r$socket" }; delete $multi->{ "w$socket" }; # AnyEvent does not support registering a socket for both # reading and writing. This is rarely used so there is no # harm in separating the events. # register read event if ( $poll == CURL_POLL_IN or $poll == CURL_POLL_INOUT ) { $multi->{ "r$socket" } = AE::io $socket, 0, sub { $multi->socket_action( $socket, CURL_CSELECT_IN ); }; } # register write event if ( $poll == CURL_POLL_OUT or $poll == CURL_POLL_INOUT ) { $multi->{ "w$socket" } = AE::io $socket, 1, sub { $multi->socket_action( $socket, CURL_CSELECT_OUT ); }; } return 1; } # timer callback: It triggers timeout update. Timeout value tells # us how soon socket_action must be called if there were no actions # on sockets. This will allow curl to trigger timeout events. sub _cb_timer { my ( $multi, $timeout_ms ) = @_; #warn "on_timer( $timeout_ms )\n"; # deregister old timer delete $multi->{timer}; my $cb = sub { $multi->socket_action( Net::Curl::Multi::CURL_SOCKET_TIMEOUT ); }; if ( $timeout_ms < 0 ) { # Negative timeout means there is no timeout at all. # Normally happens if there are no handles anymore. # # However, curl_multi_timeout(3) says: # # Note: if libcurl returns a -1 timeout here, it just means # that libcurl currently has no stored timeout value. You # must not wait too long (more than a few seconds perhaps) # before you call curl_multi_perform() again. if ( $multi->handles ) { $multi->{timer} = AE::timer 10, 10, $cb; } } else { # This will trigger timeouts if there are any. $multi->{timer} = AE::timer $timeout_ms / 1000, 0, $cb; } return 1; } # add one handle and kickstart download sub add_handle($$) { my $multi = shift; my $easy = shift; die "easy cannot finish()\n" unless $easy->can( 'finish' ); # Calling socket_action with default arguments will trigger # socket callback and register IO events. # # It _must_ be called _after_ add_handle(); AE will take care # of that. # # We are delaying the call because in some cases socket_action # may finish inmediatelly (i.e. there was some error or we used # persistent connections and server returned data right away) # and it could confuse our application -- it would appear to # have finished before it started. AE::timer 0, 0, sub { $multi->socket_action(); }; $multi->SUPER::add_handle( $easy ); } # perform and call any callbacks that have finished sub socket_action { my $multi = shift; my $active = $multi->SUPER::socket_action( @_ ); return if $multi->{active} == $active; $multi->{active} = $active; while ( my ( $msg, $easy, $result ) = $multi->info_read() ) { if ( $msg == Net::Curl::Multi::CURLMSG_DONE ) { $multi->remove_handle( $easy ); $easy->finish( $result ); } else { die "I don't know what to do with message $msg.\n"; } } } 1; TEST Easy packageMulti::Event requires Easy object to provide finish() method.package Easy::Event; use strict; use warnings; use Net::Curl::Easy qw(/^CURLOPT_/); use base qw(Net::Curl::Easy); sub new { my $class = shift; my $uri = shift; my $cb = shift; my $easy = $class->SUPER::new( { uri => $uri, body => '', cb => $cb } ); $easy->setopt( CURLOPT_URL, $uri ); $easy->setopt( CURLOPT_WRITEHEADER, \$easy->{headers} ); $easy->setopt( CURLOPT_FILE, \$easy->{body} ); return $easy; } sub finish { my ( $easy, $result ) = @_; printf "\nFinished downloading %s: %s: %d bytes\n", $easy->{uri}, $result, length $easy->{body}; $easy->{cb}->( $easy->{body} ); } 1; TEST APPLICATION#!perl use strict; use warnings; use Easy::Event; use Multi::Event; use AnyEvent; my $multi = Multi::Event->new(); my $cv = AE::cv; my @uris = ( "http://www.google.com/search?q=perl", "http://www.google.com/search?q=curl", "http://www.google.com/search?q=perl+curl", ); my $i = scalar @uris; sub done { my $body = shift; # process... unless ( --$i ) { $cv->send; } } my $timer; $timer = AE::timer 0, 0.1, sub { my $uri = shift @uris; $multi->add_handle( Easy::Event->new( $uri, \&done ) ); unless ( @uris ) { undef $timer; } }; $cv->recv; exit 0; Share::ThreadsExtracted from "examples/04-share-threads.pl"This module shows how one can share http cookies and dns cache between multiple threads. MotivationThreads are evil, but some people think they are not. I want to make them a favor and show how bad threads really are.Limitations
MODULE CODEpackage Share::Threads; use threads; use threads::shared; use Thread::Semaphore; use Net::Curl::Share qw(:constants); use base qw(Net::Curl::Share); sub new { my $class = shift; # we want our private data to be shareable my %base :shared; # create a shared share object my $self :shared = $class->SUPER::new( \%base ); # share both cookies and dns $self->setopt( CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE ); $self->setopt( CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS ); # Net::Curl::Share locks each datum automatically, this will # prevent memory corruption. # # we use semaphore to lock share completely $self->{sem} = Thread::Semaphore->new(); return $self; } # this locks way too much, but works as expected sub lock { my $share = shift; $share->{sem}->down(); $share->{blocker} = threads->tid(); } sub unlock { my $share = shift; unless ( exists $share->{blocker} ) { warn "Tried to unlock share that wasn't locked\n"; return; } unless ( $share->{blocker} == threads->tid() ) { warn "Tried to unlock share from another thread\n"; return; } delete $share->{blocker}; $share->{sem}->up(); } 1; TEST Easy packageThis Easy::Threads object will block whole share object for duration of dns name resolution and until headers are completely received.package Easy::Threads; use strict; use warnings; use Net::Curl::Easy qw(/^CURLOPT_.*/); use base qw(Net::Curl::Easy); sub new { my $class = shift; my $share = shift; my $easy = $class->SUPER::new( { body => '', head => '' } ); $easy->setopt( CURLOPT_VERBOSE, 1 ); $easy->setopt( CURLOPT_WRITEHEADER, \$easy->{head} ); $easy->setopt( CURLOPT_FILE, \$easy->{body} ); $easy->setopt( CURLOPT_HEADERFUNCTION, \&cb_header ); $easy->setopt( CURLOPT_SHARE, $share ); return $easy; } sub cb_header { my ( $easy, $data, $uservar ) = @_; if ( $data eq "\r\n" ) { # we have all the headers now, allow other threads to run $easy->share->unlock() unless $easy->{unlocked}; $easy->{unlocked} = 1; } $$uservar .= $data; return length $data; } sub get { my $easy = shift; my $uri = shift; $easy->setopt( CURLOPT_URL, $uri ); $easy->{uri} = $uri; $easy->{body} = ''; $easy->{head} = ''; delete $easy->{unlocked}; # lock share $easy->share->lock(); # ok, now we can request eval { $easy->perform(); }; # There may have been some problem, make sure we unlock the share. # This should issue a warning, check $easy->{unlocked} to see # whether we really need to unlock. $easy->share->unlock(); # return something return $easy->{body}; } 1; TEST APPLICATIONSample application using this module looks like this:#!perl use threads; use threads::shared; use strict; use warnings; use Share::Threads; use Easy::Threads; my $share :shared = Share::Threads->new(); my @uri = ( "http://www.google.com/search?q=perl", "http://www.google.com/search?q=curl", "http://www.google.com/search?q=perl+curl", "http://www.google.com/search?q=perl+threads", "http://www.google.com/search?q=curl+threads", "http://www.google.com/search?q=perl+curl+threads", ); sub getone { my $uri = shift; my $easy = Easy::Threads->new( $share ); return $easy->get( $uri ); } # start all threads my @threads; foreach my $uri ( @uri ) { push @threads, threads->create( \&getone, $uri ); threads->yield(); } # reap all threads foreach my $t ( @threads ) { my $body = $t->join(); my $len = length $body; print "DONE: [[[ $len ]]]\n"; } Irssi async downloaderExtracted from "examples/05-irssi-downloader.pl"This module implements asynchronous file fetcher for Irssi. MotivationIrssi provides a set of nice io and timer handlers, but using them may be painful sometimes. This code provides a working downloader solution.InstalationSave it in your "~/.irssi/scripts" directory as "downloader.pl" for instance. Make sure module is loaded before any script that may use it.MODULE CODE# Irssi will provide a package name and it must be left unchanged #package Irssi::Script::downloader; use strict; use Irssi (); use Net::Curl::Multi qw(/^CURL_POLL_/ /^CURL_CSELECT_/); use base qw(Net::Curl::Multi); BEGIN { if ( not Net::Curl::Multi->can( 'CURLMOPT_TIMERFUNCTION' ) ) { die "Net::Curl::Multi is missing timer callback,\n" . "rebuild Net::Curl with libcurl 7.16.0 or newer\n"; } } sub new { my $class = shift; my $multi = $class->SUPER::new(); $multi->setopt( Net::Curl::Multi::CURLMOPT_SOCKETFUNCTION, \&_cb_socket ); $multi->setopt( Net::Curl::Multi::CURLMOPT_TIMERFUNCTION, \&_cb_timer ); $multi->{active} = -1; return $multi; } sub _cb_socket { my ( $multi, $easy, $socket, $poll ) = @_; # deregister old io events if ( exists $multi->{ "io$socket" } ) { Irssi::input_remove( delete $multi->{ "io$socket" } ); } my $cond = 0; my $action = 0; if ( $poll == CURL_POLL_IN ) { $cond = Irssi::INPUT_READ(); $action = CURL_CSELECT_IN; } elsif ( $poll == CURL_POLL_OUT ) { $cond = Irssi::INPUT_WRITE(); $action = CURL_CSELECT_OUT; } elsif ( $poll == CURL_POLL_INOUT ) { $cond = Irssi::INPUT_READ() | Irssi::INPUT_WRITE(); # we don't know whether it can read or write, # so let libcurl figure it out $action = 0; } else { return 1; } $multi->{ "io$socket" } = Irssi::input_add( $socket, $cond, sub { $multi->socket_action( $socket, $action ); }, '' ); return 1; } sub _cb_timer { my ( $multi, $timeout_ms ) = @_; # deregister old timer if ( exists $multi->{timer} ) { Irssi::timeout_remove( delete $multi->{timer} ); } my $cb = sub { $multi->socket_action( Net::Curl::Multi::CURL_SOCKET_TIMEOUT ); }; if ( $timeout_ms < 0 ) { if ( $multi->handles ) { # we don't know what the timeout is $multi->{timer} = Irssi::timeout_add( 10000, $cb, '' ); } } else { # Irssi won't allow smaller timeouts $timeout_ms = 10 if $timeout_ms < 10; $multi->{timer} = Irssi::timeout_add_once( $timeout_ms, $cb, '' ); } return 1; } sub add_handle($$) { my $multi = shift; my $easy = shift; die "easy cannot finish()\n" unless $easy->can( 'finish' ); # Irssi won't allow timeout smaller than 10ms Irssi::timeout_add_once( 10, sub { $multi->socket_action(); }, '' ); $multi->{active} = -1; $multi->SUPER::add_handle( $easy ); } # perform and call any callbacks that have finished sub socket_action { my $multi = shift; my $active = $multi->SUPER::socket_action( @_ ); return if $multi->{active} == $active; $multi->{active} = $active; while ( my ( $msg, $easy, $result ) = $multi->info_read() ) { if ( $msg == Net::Curl::Multi::CURLMSG_DONE ) { $multi->remove_handle( $easy ); $easy->finish( $result ); } else { die "I don't know what to do with message $msg.\n"; } } } # we use just one global multi object my $multi; # put the add() function in some package we know sub Net::Curl::Multi::add($) { unless ( $multi ) { $multi = __PACKAGE__->new(); } $multi->add_handle( shift ); } package Irssi::Curl::Easy; use strict; use warnings; use Net::Curl; use Net::Curl::Easy qw(/^CURLOPT_/); use base qw(Net::Curl::Easy); my $has_zlib = ( Net::Curl::version_info()->{features} & Net::Curl::CURL_VERSION_LIBZ ) != 0; sub new { my $class = shift; my $uri = shift; my $cb = shift; my $easy = $class->SUPER::new( { body => '', headers => '' } ); # some sane defaults $easy->setopt( CURLOPT_WRITEHEADER, \$easy->{headers} ); $easy->setopt( CURLOPT_FILE, \$easy->{body} ); $easy->setopt( CURLOPT_TIMEOUT, 300 ); $easy->setopt( CURLOPT_CONNECTTIMEOUT, 60 ); $easy->setopt( CURLOPT_MAXREDIRS, 20 ); $easy->setopt( CURLOPT_FOLLOWLOCATION, 1 ); $easy->setopt( CURLOPT_ENCODING, 'gzip,deflate' ) if $has_zlib; $easy->setopt( CURLOPT_SSL_VERIFYPEER, 0 ); $easy->setopt( CURLOPT_COOKIEFILE, '' ); $easy->setopt( CURLOPT_USERAGENT, 'Irssi + Net::Curl' ); return $easy; } sub finish { my ( $easy, $result ) = @_; $easy->{referer} = $easy->getinfo( Net::Curl::Easy::CURLINFO_EFFECTIVE_URL ); my $cb = $easy->{cb}; $cb->( $easy, $result ); } sub _common_add { my ( $easy, $uri, $cb ) = @_; if ( $easy->{referer} ) { $easy->setopt( CURLOPT_REFERER, $easy->{referer} ); } $easy->setopt( CURLOPT_URL, $uri ); $easy->{uri} = $uri; $easy->{cb} = $cb; $easy->{body} = ''; $easy->{headers} = ''; Net::Curl::Multi::add( $easy ); } # get some uri sub get { my ( $easy, $uri, $cb ) = @_; $easy->setopt( CURLOPT_HTTPGET, 1 ); $easy->_common_add( $uri, $cb ); } # request head on some uri sub head { my ( $easy, $uri, $cb ) = @_; $easy->setopt( CURLOPT_NOBODY, 1 ); $easy->_common_add( $uri, $cb ); } # post data to some uri sub post { my ( $easy, $uri, $cb, $post ) = @_; $easy->setopt( CURLOPT_POST, 1 ); $easy->setopt( CURLOPT_POSTFIELDS, $post ); $easy->setopt( CURLOPT_POSTFIELDSIZE, length $post ); $easy->_common_add( $uri, $cb ); } # get new downloader object sub Irssi::downloader { return __PACKAGE__->new(); } EXAMPLE SCRIPTThis script will load downloader module automatically, if it has been named "downloader.pl".use strict; use warnings; use Irssi; use IO::File; use URI::Escape; Irssi::command( '/script load downloader.pl' ); sub got_body { my ( $window, $easy, $result ) = @_; if ( $result ) { warn "Could not download $easy->{uri}: $result\n"; return; } my @found; while ( $easy->{body} =~ s#<h2\s+class=sr><a\s+href="(.*?)"> <b>(.*?)</b></a></h2>##x ) { my $uri = $1; $_ = $2; s/&#(\d+);/chr $1/eg; chomp; push @found, $_; } @found = "no results" unless @found; my $msg = "CPAN search %9$easy->{args}%n: " . (join "%9;%n ", @found); if ( $window ) { $window->print( $msg ); } else { Irssi::print( $msg ); } } sub cpan_search { my ( $args, $server, $window ) = @_; my $query = uri_escape( $args ); my $uri = "http://search.cpan.org/search?query=${query}&mode=all"; my $easy = Irssi::downloader(); $easy->{args} = $args; $easy->get( $uri, sub { got_body( $window, @_ ) } ); } Irssi::command_bind( 'cpan', \&cpan_search );
Visit the GSP FreeBSD Man Page Interface. |