Apache2::CPAN::Search - mod_perl interface to CPAN::Search::Lite::Query
This module provides a mod_perl (2) interface to CPAN::Search::Lite::Query.
The modules Apache2::Request
and Apache2::Cookie
of the libapreq2
distribution
are required. A directive
PerlLoadModule Apache2::CPAN::Search
should appear before any of the Location
directives
using the module. As well, the following directives should
be defined in the Apache configuration file.
CSL_db database
CSL_user user
CSL_passwd password
CSL_user
.]
CSL_tt2 /path/to/tt2
CSL_dl http://www.cpan.org
CSL_max_results 200
The response handler can then be specified as
<Location "/search"> SetHandler perl-script PerlResponseHandler Apache2::CPAN::Search->search </Location>
A request for http://localhost/search
without any
query string will bring up a page of chapterid listings.
All other requests are handled through the query string
arguments.
mode=$value
$value
of mode
mode=dist
, mode=author
, mode=module
mode=chapter
mode=mirror
mode=$mode;query=$query
$mode
must be one of dist
,
module
, or author
. A search using the specified $query
will be done on, respectively, distribution names and abstracts,
module names and abstracts, and CPAN ids and full names.
mode=$mode;letter=$letter
$mode
must be one of dist
,
module
, or author
. If $letter
is a single letter,
this returns, resepctively, all
distribution names, module names, or CPAN ids beginning
with the specified letter. If $letter
is more than one
letter, all distribution names matching $letter-*
are returned,
for mode=dist
, or all module names matching $letter::*
are returned, for mode=module
.
recent=$age
$age
days.
chapterid=$id
$id
.
chapterid=$id;subchapter=$subchapter
$subchapter
in the $id
chapter.
module=$name
or mod_id=$id
dist=$name
or dist_id=$id
cpanid=$cpanid
or author=$cpanid
or auth_id=$id
Make sure to check the values of $db
, $user
,
$passwd
, and $tt2
at the top of this file.
the Apache2::CPAN::Query manpage, the CPAN::Search::Lite::Query manpage, and mod_perl.
package Apache2::CPAN::Search; use strict; use warnings; use utf8; use mod_perl2 1.999022; # sanity check for a recent version use Apache2::Const -compile => qw(OK SERVER_ERROR TAKE1 RSRC_CONF ACCESS_CONF); use CPAN::Search::Lite::Query; use CPAN::Search::Lite::Util qw($mode_info $query_info %chaps %modes $tt2_pages); our $chaps_desc = {}; our $pages = {}; use CPAN::Search::Lite::Lang qw(%langs load); use Template; use File::Spec::Functions qw(catfile catdir); use Apache2::Request; use Apache2::Cookie; use Apache2::RequestRec (); use Apache2::RequestIO (); use APR::Date; use APR::URI; use Apache2::URI; use Apache2::Module (); use Apache2::Log (); our ($VERSION); $VERSION = 0.66; my @directives = ( {name => 'CSL_db', errmsg => 'database name', args_how => Apache2::Const::TAKE1, req_override => Apache2::Const::RSRC_CONF | Apache2::Const::ACCESS_CONF, }, {name => 'CSL_user', errmsg => 'user to log in as', args_how => Apache2::Const::TAKE1, req_override => Apache2::Const::RSRC_CONF | Apache2::Const::ACCESS_CONF, }, {name => 'CSL_passwd', errmsg => 'password for user', args_how => Apache2::Const::TAKE1, req_override => Apache2::Const::RSRC_CONF | Apache2::Const::ACCESS_CONF, }, {name => 'CSL_tt2', errmsg => 'location of tt2 pages', args_how => Apache2::Const::TAKE1, req_override => Apache2::Const::RSRC_CONF | Apache2::Const::ACCESS_CONF, }, {name => 'CSL_dl', errmsg => 'default download location', args_how => Apache2::Const::TAKE1, req_override => Apache2::Const::RSRC_CONF | Apache2::Const::ACCESS_CONF, }, {name => 'CSL_max_results', errmsg => 'maximum number of results', args_how => Apache2::Const::TAKE1, req_override => Apache2::Const::RSRC_CONF | Apache2::Const::ACCESS_CONF, }, {name => 'CSL_html_root', errmsg => 'root directory of html files', args_how => Apache2::Const::TAKE1, req_override => Apache2::Const::RSRC_CONF | Apache2::Const::ACCESS_CONF, }, {name => 'CSL_html_uri', errmsg => 'root uri of html files', args_how => Apache2::Const::TAKE1, req_override => Apache2::Const::RSRC_CONF | Apache2::Const::ACCESS_CONF, }, ); Apache2::Module::add(__PACKAGE__, \@directives); my $cookie_name = 'cslmirror'; my ($template, $query, $cfg, $dl, $max_results); sub new { my ($class, $r) = @_; my $lang = lang_wanted($r); my $req = Apache2::Request->new($r); $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config) || { }; $dl = $cfg->{dl} || 'http://www.cpan.org'; $max_results ||= $cfg->{max_results} || 200; my $passwd = $cfg->{passwd} || ''; $template ||= Template->new({ INCLUDE_PATH => [$cfg->{tt2}, Template::Config->instdir('templates')], PRE_PROCESS => ['config', 'header'], POST_PROCESS => 'footer', POST_CHOMP => 1, }) || do { $r->log_error(Template->error()); return Apache2::Const::SERVER_ERROR; }; $query ||= CPAN::Search::Lite::Query->new(db => $cfg->{db}, user => $cfg->{user}, passwd => $passwd, max_results => $max_results); $CPAN::Search::Lite::Query::lang = $lang; unless ($pages->{$lang}) { my $rc = load(lang => $lang, pages => $pages, chaps_desc => $chaps_desc); unless ($rc == 1) { $r->log_error($rc); return; } } my $mode = $req->param('mode'); unless ($mode && $mode eq 'mirror') { if ($r->protocol =~ /(\d\.\d)/ && $1 >= 1.1) { $r->headers_out->{'Cache-Control'} = 'max-age=36000'; } else { $r->headers_out->{Expires} = APR::Date::parse_http(time+36000); } } my $mirror; if (my $host = ($req->param('host') || $req->param('url') )) { my $cookie = Apache2::Cookie->new($r, name => $cookie_name, value => $host, expires => '+1y'); $cookie->bake; $mirror = $host; } else { my %cookies = Apache2::Cookie->fetch($r); if (my $c = $cookies{$cookie_name}) { $mirror = $c->value; } } $mirror ||= $dl; $r->content_type('text/html; charset=UTF-8'); my $self = {mode => $mode, mirror => $mirror, req => $req, lang => $lang}; bless $self, $class; } sub search : method { my ($self, $r) = @_; $self = __PACKAGE__->new($r) unless ref($self) eq __PACKAGE__; my $req = $self->{req}; my $mode = $self->{mode}; my $query_term = trim($req->param('query')); my $letter = $req->param('letter'); my $chapterid = $req->param('chapterid'); my $recent = $req->param('recent'); my $subchapter = $req->param('subchapter'); my ($page, $results, %extra_info, $age); MODE: { (defined $mode and $mode eq 'mirror') and do { my %save; if (my $referer = $r->headers_in->{Referer}) { my $parsed = APR::URI->parse($r->pool, $referer); my $qs = $parsed->query; %save = parse_qs($qs); delete $save{host}; delete $save{url}; } $extra_info{save} = \%save; $page = 'mirror'; last MODE; }; (defined $mode and $mode eq 'chapter') and do { $results = $self->chap_results(); $page = $results ? 'chapterid' : 'missing'; last MODE; }; (defined $chapterid) and do { my %args; $args{mode} = $mode = 'chapter'; $args{id} = $chapterid; $extra_info{chapterid} = $chapterid; $extra_info{chapter_link} = $chaps{$chapterid}; $extra_info{chapter_desc} = $chaps_desc->{$self->{lang}}->{$chapterid}; if ($subchapter) { $args{subchapter} = $subchapter; $extra_info{subchapter} = $subchapter; $page = $tt2_pages->{$mode}->{search}; } else { $page = $tt2_pages->{$mode}->{info}; } $query->query(%args); $results = $query->{results}; $page = 'missing' unless $results; last MODE; }; (defined $mode and not $modes{$mode}) and do { $page = 'missing'; last MODE; }; (defined $mode and defined $query_term) and do { $mode = 'module' if $query_term =~ /::/; $query_term =~ s{\.pm$}{} if ($mode eq 'module'); $query->query(mode => $mode, query => $query_term); if ($results = $query->{results}) { $page = ref($results) eq 'ARRAY' ? $tt2_pages->{$mode}->{search} : $tt2_pages->{$mode}->{info}; } else { $page = 'missing'; } last MODE; }; (defined $mode and defined $letter) and do { $query->query(mode => $mode, letter => $letter); $results = $query->{results}; $page = $results ? $tt2_pages->{$mode}->{letter} : 'missing'; unless ($letter =~ /^\w$/) { $extra_info{subletter} = $letter; ($extra_info{letter} = $letter) =~ s/^(\w).*/$1/; } last MODE; }; (defined $recent) and do { $mode = 'dist'; $age = $recent || 7; $query->query(mode => $mode, recent => $age); $results = $query->{results}; $page = $results ? 'recent' : 'missing'; last MODE; }; (defined $mode) and do { $page = 'letters'; last MODE; }; foreach my $what (keys %$query_info) { next unless my $value = $req->param($what); $mode = $query_info->{$what}->{mode}; my $type = $query_info->{$what}->{type}; $query->query(mode => $mode, $type => $value); if ($results = $query->{results}) { $page = ref($results) eq 'ARRAY' ? $tt2_pages->{$mode}->{search} : $tt2_pages->{$mode}->{info}; } else { $page = 'missing'; } last MODE; } $mode = 'chapter'; $results = $self->chap_results(); $page = $results ? 'chapterid' : 'missing'; } unless (ref($results) eq 'ARRAY') { if (my $name = $results->{$mode_info->{$mode}->{name}}) { if ($name =~ /^(\w)(\w)/) { my ($a, $b) = (uc($1), uc($2)); $extra_info{letter} = $a; $extra_info{cpan_letter} = "$a/$a$b"; } if ($mode eq 'dist' and $name =~ /^([^-]+)-/) { $extra_info{subletter} = $1; } if ($mode eq 'module' and $name =~ /^([^:]+)::/) { $extra_info{subletter} = $1; } } } my $vars = {results => $results, query => $query_term, mode => $mode, letter => $letter, age => $age, mirror => $self->{mirror}, pages => $pages->{$self->{lang}}, %extra_info, }; if (my $error = $query->{error}) { $r->log->error($error); $query->{error} = undef; $page = 'error'; } $template->process($page, $vars, $r, binmode => ':utf8') or do { $r->log_error($template->error()); return Apache2::Const::SERVER_ERROR; }; return Apache2::Const::OK; } sub chap_results { my $chapters; foreach my $key( sort {$a <=> $b} keys %chaps) { push @$chapters, {chapterid => $key, chap_link => $chaps{$key}, chap_desc => $chaps_desc->{$self->{lang}}->{$key}, }; } return $chapters; } sub parse_qs { my $qs = shift; my %args = map { tr/+/ /; s/%([0-9a-fA-F]{2})/pack("C",hex($1))/ge; $_; } split /[=&;]/, $qs, -1; return %args; } sub trim { my $string = shift; return unless $string; $string =~ s/^\s+//; $string =~ s/\s+$//; $string =~ s/\s+/ /g; $string =~ s/\"|\'|\\//g; return ($string =~ /\w/) ? $string : undef; } sub lang_wanted { my $r = shift; my $accept = $r->headers_in->{'Accept-Language'}; return 'en' unless $accept; my %wanted; foreach my $lang(split /,/, $accept) { if ($lang !~ /;/) { $lang =~ s{(\w+)-\w+}{$1}; $wanted{1} = lc $lang; } else { my @q = split /;/, $lang, 2; $q[1] =~ s{q=}{}; $q[1] = trim($q[1]); $q[0] =~ s{(\w+)-\w+}{$1}; $wanted{$q[1]} = lc trim($q[0]); } } for (reverse sort {$a <=> $b} keys %wanted) { return $wanted{$_} if $langs{$wanted{$_}}; } return 'en'; } sub CSL_db { my ($cfg, $parms, $db) = @_; $cfg->{ db } = $db; } sub CSL_user { my ($cfg, $parms, $user) = @_; $cfg->{ user } = $user; } sub CSL_passwd { my ($cfg, $parms, $passwd) = @_; $passwd = '' unless $passwd =~ /\w/; $cfg->{ passwd } = $passwd; } sub CSL_tt2 { my ($cfg, $parms, $tt2) = @_; $cfg->{ tt2 } = $tt2; } sub CSL_dl { my ($cfg, $parms, $dl) = @_; $cfg->{ dl } = $dl; } sub CSL_max_results { my ($cfg, $parms, $max_results) = @_; $cfg->{ max_results } = $max_results; } sub CSL_html_root { my ($cfg, $parms, $html_root) = @_; $cfg->{ html_root } = $html_root; } sub CSL_html_uri { my ($cfg, $parms, $html_uri) = @_; $cfg->{ html_uri } = $html_uri; } 1; __END__