File Coverage

blib/lib/Dezi/App.pm
Criterion Covered Total %
statement 30 85 35.2
branch 0 40 0.0
condition 0 15 0.0
subroutine 10 15 66.6
pod 3 3 100.0
total 43 158 27.2


line stmt bran cond sub pod time code
1             package Dezi::App;
2 11     11   16191 use Moose;
  11         4861749  
  11         87  
3 11     11   88270 use MooseX::StrictConstructor;
  11         354596  
  11         59  
4             with 'Dezi::Role';
5 11     11   103471 use Carp;
  11         41  
  11         835  
6 11     11   5638 use Data::Dump qw( dump );
  11         35852  
  11         710  
7 11     11   63 use Scalar::Util qw( blessed );
  11         23  
  11         458  
8 11     11   61 use Class::Load ();
  11         22  
  11         228  
9 11     11   10505 use Types::Standard qw( Bool HashRef );
  11         730033  
  11         147  
10 11     11   15995 use Dezi::Types qw( DeziIndexerConfig DeziInvIndex DeziFileOrCodeRef );
  11         35  
  11         118  
11 11     11   13950 use Dezi::ReplaceRules;
  11         49  
  11         519  
12 11     11   103 use namespace::autoclean;
  11         23  
  11         161  
13              
14             our $VERSION = '0.014';
15              
16             =head1 NAME
17              
18             Dezi::App - build Dezi search applications
19              
20             =head1 SYNOPSIS
21              
22             my $app = Dezi::App->new(
23             invindex => 't/testindex',
24             aggregator => 'fs',
25             indexer => 'lucy',
26             config => 't/test.conf',
27             filter => sub { diag( "doc filter on " . $_[0]->url ) },
28             );
29              
30             my $count = $app->run('path/to/files');
31              
32             printf("Indexed %d documents\n", $count);
33              
34             =head1 DESCRIPTION
35              
36             Dezi::App is convenience class for building search applications.
37             It provides shortcuts for pulling together all the Dezi::App components
38             into a single App object.
39              
40             Dezi::App depends upon:
41              
42             =over
43              
44             =item
45              
46             L<Dezi::InvIndex>
47              
48             =item
49              
50             L<Dezi::Indexer>
51              
52             =item
53              
54             L<Dezi::Aggregator>
55              
56             =item
57              
58             L<Dezi::Indexer::Config>
59              
60             =back
61              
62             =head1 METHODS
63              
64             The following attributes are available as params to new() and
65             instance methods:
66              
67             =over
68              
69             =item aggregator
70              
71             Expects a Dezi::Aggregator instance or a shortcut string. The shortcuts
72             are:
73              
74             =over
75              
76             =item fs
77              
78             Filesystem -- see L<Dezi::Aggregator::FS>.
79              
80             =item spider
81              
82             Web crawler -- see L<Dezi::Aggregator::Spider>.
83              
84             =item mail
85              
86             Mail::Box reader -- see L<Dezi::Aggregator::Mail>.
87              
88             =item mailfs
89              
90             Mail::Box + filesystem -- see L<Dezi::Aggregator::MailFS>.
91              
92             =back
93              
94             =item aggregator_opts
95              
96             Hashref passed to aggregator->new.
97              
98             =item config
99              
100             String or Path::Class::File object pointing at config file,
101             or a Dezi::Indexer::Config object.
102              
103             =item indexer
104              
105             Shortcut string or Dezi::Indexer instance. Shortcuts include:
106              
107             =over
108              
109             =item lucy
110              
111             L<Dezi::Lucy::Indexer>
112              
113             =item dbi
114              
115             TODO
116              
117             =item xapian
118              
119             TODO
120              
121             =item test
122              
123             L<Dezi::Test::Indexer>
124              
125             =back
126              
127             =item indexer_opts
128              
129             Hashref passed directly to indexer->new.
130              
131             =item invindex
132              
133             String or Path::Class::Dir pointing at index directory,
134             or a L<Dezi::InvIndex> instance.
135              
136             =item filter
137              
138             A CODE reference, or a string or Path::Class::File object
139             pointing at a file containing a CODE reference
140             that can be loaded with do().
141              
142             =item test_mode
143              
144             Boolean turning off the indexer, running only the aggregator.
145             Default is false (off).
146              
147             =back
148              
149             =cut
150              
151             has 'aggregator' => ( is => 'rw', ); # we do our own isa check
152             has 'aggregator_opts' => ( is => 'rw', isa => HashRef );
153             has 'config' => ( is => 'rw', isa => DeziIndexerConfig, coerce => 1, );
154             has 'indexer' => ( is => 'rw', ); # we do our own isa check
155             has 'indexer_opts' => ( is => 'rw', isa => HashRef );
156             has 'invindex' => (
157             is => 'rw',
158             isa => DeziInvIndex,
159             coerce => 1,
160             );
161             has 'filter' => (
162             is => 'rw',
163             isa => DeziFileOrCodeRef,
164             coerce => 1,
165             );
166             has 'test_mode' => ( is => 'rw', isa => Bool, default => 0 );
167              
168             # allow for short names. we map to class->new
169             my %ashort = (
170             fs => 'Dezi::Aggregator::FS',
171             mail => 'Dezi::Aggregator::Mail',
172             mailfs => 'Dezi::Aggregator::MailFS',
173             dbi => 'Dezi::Aggregator::DBI',
174             spider => 'Dezi::Aggregator::Spider',
175             object => 'Dezi::Aggregator::Object',
176             );
177             my %ishort = (
178             xapian => 'Dezi::Xapian::Indexer',
179             lucy => 'Dezi::Lucy::Indexer',
180             dbi => 'Dezi::DBI::Indexer',
181             test => 'Dezi::Test::Indexer',
182             );
183              
184             =head2 BUILD
185              
186             Internal method called by new(). Initializes the App object.
187              
188             =cut
189              
190             sub BUILD {
191 0     0 1   my $self = shift;
192              
193             # need to make sure we have an aggregator.
194             # indexer and/or config might already be set in aggregator
195             # but if set here, we override.
196              
197 0           my ( $aggregator, $indexer );
198              
199             # ok if undef
200 0           my $config = $self->{config};
201              
202             # get indexer
203 0   0       $indexer = $self->{indexer} || 'lucy';
204 0 0 0       if ( $self->{aggregator} and blessed( $self->{aggregator} ) ) {
205 0           $indexer = $self->{aggregator}->indexer;
206 0           $config = $self->{aggregator}->config;
207             }
208 0 0         if ( !blessed($indexer) ) {
    0          
209              
210 0 0         if ( exists $ishort{$indexer} ) {
211 0           $indexer = $ishort{$indexer};
212             }
213              
214 0 0         $self->debug and warn "creating indexer: $indexer";
215 0           Class::Load::load_class($indexer);
216              
217             my %indexer_opts = (
218             debug => $self->debug,
219             invindex => $self->{invindex}, # may be undef
220             verbose => $self->verbose,
221             config => $config, # may be undef
222             test_mode => $self->test_mode,
223 0 0         %{ $self->indexer_opts || {} },
  0            
224             );
225              
226 0 0         $self->debug and warn "indexer opts: " . dump( \%indexer_opts );
227              
228 0           $indexer = $indexer->new(%indexer_opts);
229             }
230             elsif ( !$indexer->isa('Dezi::Indexer') ) {
231 0           confess "$indexer is not a Dezi::Indexer-derived object";
232             }
233              
234 0   0       $aggregator = $self->{aggregator} || 'fs';
235 0   0       my $aggregator_opts = $self->aggregator_opts || {};
236              
237 0 0         if ( !blessed($aggregator) ) {
    0          
238              
239 0 0         if ( exists $ashort{$aggregator} ) {
240 0           $aggregator = $ashort{$aggregator};
241             }
242              
243 0 0         $self->debug and warn "creating aggregator: $aggregator";
244 0           Class::Load::load_class($aggregator);
245              
246 0           my %aggr_opts = (
247             indexer => $indexer,
248             debug => $self->debug,
249             verbose => $self->verbose,
250             test_mode => $self->test_mode,
251             %$aggregator_opts,
252             );
253              
254 0 0         $self->debug and warn "aggregator opts: " . dump( \%aggr_opts );
255              
256 0           $aggregator = $aggregator->new(%aggr_opts);
257             }
258             elsif ( !$aggregator->isa('Dezi::Aggregator') ) {
259 0           confess "$aggregator is not a Dezi::Aggregator-derived object";
260             }
261              
262             # set these now so we can call $self->config
263 0           $self->{aggregator} = $aggregator;
264 0           $self->{indexer} = $indexer;
265              
266 0 0 0       if ( $indexer and $indexer->config and $indexer->config->ReplaceRules ) {
      0        
267              
268             # create a CODE ref that uses the ReplaceRules
269 0           my $rr = $indexer->config->ReplaceRules;
270 0           my $rules = Dezi::ReplaceRules->new(@$rr);
271 0 0         if ( $self->filter ) {
272 0           my $filter_copy = $self->filter;
273             $self->filter(
274             sub {
275 0     0     $_[0]->url( $rules->apply( $_[0]->url ) );
276 0           $filter_copy->( $_[0] );
277             }
278 0           );
279             }
280             else {
281             $self->filter(
282             sub {
283 0     0     $_[0]->url( $rules->apply( $_[0]->url ) );
284             }
285 0           );
286             }
287             }
288              
289 0 0         if ( $self->filter ) {
290 0           $aggregator->set_filter( $self->filter );
291             }
292              
293             $indexer->{test_mode} = $self->{test_mode}
294 0 0         unless exists $indexer->{test_mode};
295             $aggregator->{test_mode} = $self->{test_mode}
296 0 0         unless exists $aggregator->{test_mode};
297              
298 0 0         $self->debug and carp dump $self;
299              
300 0           return $self;
301             }
302              
303             =head2 run( I<paths> )
304              
305             Run the app on I<paths>. I<paths> may be URLs, filesystem paths,
306             or whatever the Aggregator expects.
307              
308             Returns the Indexer count.
309              
310             =cut
311              
312             sub run {
313 0     0 1   my $self = shift;
314 0 0         my $aggregator = $self->aggregator or confess 'aggregator required';
315 0 0         unless ( $aggregator->isa('Dezi::Aggregator') ) {
316 0           croak "aggregator is not a Dezi::Aggregator";
317             }
318              
319 0           $aggregator->indexer->start;
320 0           $aggregator->crawl(@_);
321 0           $aggregator->indexer->finish;
322 0           return $aggregator->indexer->count;
323             }
324              
325             =head2 count
326              
327             Returns the indexer's count. B<NOTE> This is the number of documents
328             actually indexed, not counting the number of documents considered and
329             discarded by the aggregator. If you want the number of documents
330             the aggregator looked at, regardless of whether they were indexed,
331             use the aggregator's count() method.
332              
333             =cut
334              
335             sub count {
336 0     0 1   shift->indexer->count;
337             }
338              
339             __PACKAGE__->meta->make_immutable;
340              
341             1;
342              
343             __END__
344              
345             =head1 AUTHOR
346              
347             Peter Karman, E<lt>karpet@dezi.orgE<gt>
348              
349             =head1 BUGS
350              
351             Please report any bugs or feature requests to C<bug-dezi-app at rt.cpan.org>, or through
352             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dezi-App>.
353             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
354              
355             =head1 SUPPORT
356              
357             You can find documentation for this module with the perldoc command.
358              
359             perldoc Dezi::App
360              
361             You can also look for information at:
362              
363             =over 4
364              
365             =item * Website
366              
367             L<http://dezi.org/>
368              
369             =item * IRC
370              
371             #dezisearch at freenode
372              
373             =item * Mailing list
374              
375             L<https://groups.google.com/forum/#!forum/dezi-search>
376              
377             =item * RT: CPAN's request tracker
378              
379             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Dezi-App>
380              
381             =item * AnnoCPAN: Annotated CPAN documentation
382              
383             L<http://annocpan.org/dist/Dezi-App>
384              
385             =item * CPAN Ratings
386              
387             L<http://cpanratings.perl.org/d/Dezi-App>
388              
389             =item * Search CPAN
390              
391             L<https://metacpan.org/dist/Dezi-App/>
392              
393             =back
394              
395             =head1 COPYRIGHT AND LICENSE
396              
397             Copyright 2014 by Peter Karman
398              
399             This library is free software; you can redistribute it and/or modify
400             it under the terms of the GPL v2 or later.
401              
402             =head1 SEE ALSO
403              
404             L<http://dezi.org/>, L<http://swish-e.org/>, L<http://lucy.apache.org/>