File Coverage

blib/lib/WWW/Comix.pm
Criterion Covered Total %
statement 18 68 26.4
branch 0 20 0.0
condition 0 9 0.0
subroutine 6 15 40.0
pod 6 6 100.0
total 30 118 25.4


line stmt bran cond sub pod time code
1             package WWW::Comix;
2              
3 1     1   39618 use version; our $VERSION = qv('0.1.1');
  1         2275  
  1         6  
4              
5 1     1   75 use warnings;
  1         1  
  1         30  
6 1     1   4 use strict;
  1         6  
  1         20  
7 1     1   4 use Carp;
  1         1  
  1         95  
8 1     1   964 use English qw( -no_match_vars );
  1         5046  
  1         7  
9              
10             =for comment
11             There is some blur about what "plugin" means here, because you can
12             refer to a plugin either by name (and this is what you see from the
13             outside) or by package (most operations rely on this in this module).
14             To help distinguish the two cases, all package-oriented functions
15             are prepended with an underscore, just to mark that these functions
16             should remain private here. Caveat emptor.
17              
18             =cut
19              
20 1     1   1686 use Module::Pluggable require => 1, sub_name => '_unsorted_plugins';
  1         12316  
  1         8  
21              
22             sub new {
23 0     0 1   my $package = shift;
24 0           my %args = @_;
25              
26 0 0 0       croak "no comic specified\n"
27             unless exists $args{comic} && defined($args{comic});
28              
29 0 0         if (exists $args{plugin}) {
30 0           my $plugin = _plugin_from_name($args{plugin});
31 0           return $plugin->new(@_);
32             }
33              
34 0           for my $plugin (_sorted_plugins()) {
35 0           my $agent = eval { $plugin->new(@_); };
  0            
36 0 0         carp $EVAL_ERROR if $EVAL_ERROR;
37 0 0 0       next unless $agent && $agent->is_ok();
38 0           return $agent;
39             } ## end for my $plugin (_sorted_plugins...
40              
41 0           croak "comic $args{comic} not available";
42             } ## end sub new
43              
44             sub get_plugins {
45 0     0 1   my @plugins = map { $_->get_name() } _unsorted_plugins();
  0            
46 0 0         return @plugins if wantarray;
47 0           return \@plugins;
48             }
49              
50             sub get_plugins_capabilities {
51 0     0 1   my $package = shift;
52 0           my %args = @_;
53              
54 0           my %comic_for;
55 0           for my $plugin (_unsorted_plugins()) {
56 0 0         eval {
57 0           $comic_for{$plugin->get_name()} =
58             [sort $plugin->get_comics_list(@_)];
59             } or carp $EVAL_ERROR;
60             } ## end for my $plugin (_unsorted_plugins...
61              
62 0 0         return %comic_for if wantarray;
63 0           return \%comic_for;
64             } ## end sub get_plugins_capabilities
65              
66             sub get_comics_list {
67 0     0 1   my $package = shift;
68              
69 0           my %plugin_for;
70 0           for my $plugin (_sorted_plugins()) {
71 0 0         eval {
72 0           my $plugin_name = $plugin->get_name();
73 0           for my $comic ($plugin->get_comics_list(@_)) {
74 0           push @{$plugin_for{$comic}}, $plugin_name;
  0            
75             }
76 0           1;
77             }
78             or carp $EVAL_ERROR;
79             } ## end for my $plugin (_sorted_plugins...
80              
81 0 0         return %plugin_for if wantarray;
82 0           return \%plugin_for;
83             } ## end sub get_comics_list
84              
85             sub probe {
86 0     0 1   $_[0]->get_plugins_capabilities(probe => 'ok');
87             }
88              
89             sub _plugin_from_name {
90 0     0     my $pack = shift;
91 0   0       my $name = shift || $pack;
92 0           for (_unsorted_plugins()) {
93 0 0         return $_ if $name eq $_->get_name();
94             }
95 0           croak "no plugin for '$name'";
96             } ## end sub _plugin_from_name
97              
98             sub _get_plugins_priorities { # package-wise
99 0     0     map { $_ => $_->get_priority() } _unsorted_plugins();
  0            
100             }
101              
102             sub get_plugins_priorities { # name-wise
103 0     0 1   my %priority_for = _get_plugins_priorities();
104 0           map { $_->get_name() => $priority_for{$_} } keys %priority_for;
  0            
105             }
106              
107             sub _sorted_plugins {
108 0     0     my %priority_for = _get_plugins_priorities();
109             return
110 0           sort { $priority_for{$a} <=> $priority_for{$b} } _unsorted_plugins();
  0            
111             }
112              
113             1; # Magic true value required at end of module
114             __END__
115              
116             =head1 NAME
117              
118             WWW::Comix - programmatically access comics on the web
119              
120             =head1 VERSION
121              
122             This document describes WWW::Comix version 0.1.1. Most likely, this
123             version number here is outdate, and you should peek the source.
124              
125              
126             =head1 SYNOPSIS
127              
128             use WWW::Comix;
129              
130             # List of available plugins
131             my @available_plugins = WWW::Comix->get_plugins();
132              
133             # List of plugins and their comics
134             my %comics_for = WWW::Comix->get_plugins_list(probe => 'ok');
135             while (my ($name, $comics) = each %comics_for) {
136             print {*STDOUT} "$plugin\n";
137             print {*STDOUT} " $_\n" for @$comics;
138             }
139              
140             # List comics by plugins that provide them, in order of priority
141             my %plugins_for = WWW::Comix->get_comics_list(probe => 'ok');
142             while (my ($comic, $plugin) = each %plugin_for) {
143             print {*STDOUT} $comic, ': ';
144             print {*STDOUT} join ', ', @$plugin;
145             }
146              
147             # So what's the priority of each plugin?
148             my %priority_for = WWW::Comix->get_plugins_priorities();
149              
150             # Ok, I have a $feature I'm interested into...
151             WWW::Comix->probe();
152             my $comix = WWW::Comix->new(comic => $feature);
153             my $iterator = $comix->get_id_iterator();
154             while (my $id = $iterator->()) {
155             my $blob = $comix->get(id => $id);
156             # ... $blob contains the image data...
157             }
158              
159             # I'd like to save it immediately, in directory /path/to/comix
160             WWW::Comix->probe(); # only one probe is necessary!
161             my $comix = WWW::Comix->new(
162             comic => $feature, directory => '/path/to/comix');
163             my $iterator = $comix->get_id_iterator();
164             while (my $id = $iterator->()) {
165             my $filename = $comix->getstore(id => $id);
166             # filename is somewhat guessed but should be ok most times
167             # see docs for ways to set your filename/filename rules
168             }
169              
170              
171             =head1 DESCRIPTION
172              
173             This modules eases the programmatical access to comic publishing sites.
174             It deals with the differences in any of them, providing you with an
175             abstraction layer that hides all the weird bits. New sites can be
176             added easily by means of its plugin system.
177              
178             The philosophy, and many ideas, have been taken by the excellent
179             L<WWW::Comic> by Nicola Worthington. In particular, the idea of
180             "probing" and the general organisation of the plugins is more or less
181             the same. Why another module then? The main thing that is lacking in
182             L<WWW::Comic> is a way to programmatically access the whole list
183             of available comics in a site.
184              
185             In particular, L<WWW::Comic> allows you to specify an id for the feature
186             you're interested into, but when it comes to knowing which ids are
187             actually available you're on your own. L<WWW::Comix> fills this gap.
188              
189             This module acts as a front-end towards the various plugins that do
190             the actual work behind the scenes. To get an "agent" for comic download
191             you'll need to know - ehr - which comic you're interested into:
192              
193             my $comix = WWW::Comix->new(comic => $feature, probe => 'ok');
194              
195             Whether you need to probe or not depends on your application. If you
196             already probed before, chances are that you don't need to do that
197             again.
198              
199             Every plugin behaves the same, and you should take a look to
200             L<WWW::Comix::Plugin> to see the exact behaviour. Anyway, you can
201             access the list of available ids in basically two manners:
202              
203             =over
204              
205             =item B<< $comix->get_available_ids() >>
206              
207             gives the full list of available comics, and
208              
209             =item B<< $comix->get_iterator() >>
210              
211             gives you an iterator, i.e. a sub reference that will give out
212             an id each time you call it, until there's no more in which case
213             it will give back C<undef>.
214              
215             =back
216              
217             Again, whether it's better for you to use one or another depends
218             entirely on your application. In general, the B<iterator> way is
219             safer, because some providers can have very long lists, spread over
220             many pages, so getting the full list can be heavy. When in doubt,
221             use the iterator.
222              
223             Now that you have one (or more) ids, you only have to grab the comics
224             you need. You can either get the image's data:
225              
226             my $blob = $comix->get(id => $id);
227              
228             or save it directly to a file:
229              
230             my $filename = $comix->getstore(id => $id);
231              
232             Plugins (should) do their best to guess the filename correctly, but
233             you can get in the loop anyway:
234              
235             $comix->getstore(id => $id, filename => 'whatever.png');
236              
237             By default, C<getstore> saves in the current directory, but you can
238             provide a C<directory> parameter to the constructor, or set it later:
239              
240             my $comix = WWW::Comix->new(
241             comic => $feature,
242             probe => 'ok',
243             directory => '/path/to/somewhere',
244             );
245             $comix->set_directory('/path/to/somewhere/else');
246              
247             See L<WWW::Comix::Plugin> if you need more flexibility.
248              
249             =head2 An Important Note
250              
251             Beware that there's a difference between the tool and using the tool.
252              
253             Whether you're allowed to use this module, and the tools that come with
254             it, is entirely up to you. This collection of modules gives you a
255             framework for accessing comics programmatically, shaping it around a
256             metaphor that proves to be effective in the most popular comics sites.
257              
258             On the other hand, the fact that these pieces of software are there
259             does not mean that you're allowed to use them. You should peruse the
260             documentation of every and each site before deciding that you can
261             use it; moreover, when you do it you understand that you'll be the
262             sole responsible. In poor's man words, if the rules of the particular
263             site say that you're not allowed to systematically download features,
264             or access the site with anything different from a web browser, than
265             you should either get permissions or refrain from using the module.
266             Note that I don't even support the idea that this module, and the tools
267             that come with it, can be regarded as a browser.
268              
269             If you're even in doubt about your possibility to use it, chances are
270             that you're not allowed to do, so I urge you B<NOT> to use it. See also
271             the L</DISCLAIMER OF WARRANTY> and L</NEGAZIONE DELLA GARANZIA>.
272              
273              
274             =head1 INTERFACE
275              
276             All the following subs are package methods, so you should invoke them
277             using the object-oriented style.
278              
279             =over
280              
281             =item B<< new >>
282              
283             get a handler to some object capable of dealing with a specific comic.
284              
285             Returns a reference if successful, croaks otherwise.
286              
287             Arguments include:
288              
289             =over
290              
291             =item B<< comic >> (mandatory)
292              
293             the comic you want to interact with;
294              
295             =item B<< plugin >> (optional)
296              
297             the plugin you want to use. Usually, WWW::Comix will try to find out
298             the best plugin for handling a specific comic, but you might want to
299             override its choice.
300              
301             =item B<< probe >> (optional)
302              
303             request the plugins to probe the respective web pages;
304              
305             =item I<< any other >>
306              
307             all other parameters are passed to the actual plugin constructor,
308             see the relevant page for any additional information.
309              
310             =back
311              
312             =item B<< probe >>
313              
314             command a probing on all available plugins. This means that each of them
315             will likely access the site it's bound to in order to retrieve the relevant
316             information about available comics.
317              
318             Does not return anything meaningful. Does not need any parameter.
319              
320             =item B<< get_plugins >>
321              
322             get the list of available plugins.
323              
324             Returns the list of plugins in list context, a reference to an array
325             with the list in scalar context. Does not need parameters.
326              
327             This method is independent of the probing status.
328              
329             =item B<< get_plugins_priorities >>
330              
331             get the priority associated to each plugin.
332              
333             Each plugin is associated to a priority. The lower the priority, the first
334             the plugin is tried in a quest to find out who deals with a specific
335             feature. The rationale is that many sites have overlaps on the comics
336             they offer, but some might offer a longer archive (hence a better
337             priority).
338              
339             Each plugin "publishes" its priority, so this is actually nothing you
340             have to worry about: it's all done automatically.
341              
342             In list context,
343             returns a hash with the associations between the plugin names and their
344             priorities. Returns a reference to the hash in scalar context. Does not
345             need parameters.
346              
347             =item B<< get_plugins_capabilities >>
348              
349             get the comics that each plugin is capable of providing.
350              
351             Returns a hash with the plugin names as keys, and a reference to an array
352             with the supported comics as values, in list context. Returns a reference
353             to the hash in scalar context.
354              
355             Most likely, you
356             will need to have L</probe>d before, or you have to specify the C<probe>
357             parameter to get meaningful results, but it might depend on the
358             particular feature you're interested into (and in the particular plugin that
359             is able to handle the feature itself).
360              
361             =item B<< get_comics_list >>
362              
363             get the list of all supported comics, with the plugins that are able
364             to provide them.
365              
366             This is some counterpart to L</get_plugins_capabilities>. For each comic,
367             a list of the available providers is established, where "better" plugins
368             (in the sense of better priorities) come first.
369              
370             In list context,
371             returns a hash with the comic names as keys, and an array reference to the
372             providers for each of them as values. Returns a reference to the hash in
373             scalar context.
374              
375             Most likely, you
376             will need to have L</probe>d before, or you have to specify the C<probe>
377             parameter to get meaningful results, but it might depend on the
378             particular feature you're interested into (and in the particular plugin that
379             is able to handle the feature itself).
380              
381             =back
382              
383             =head1 DIAGNOSTICS
384              
385             As a rule of thumb, every time there's an error you will get an
386             exception. Exceptions are thrown with C<croak>, warnings with C<carp>.
387              
388             =head2 Exceptions
389              
390             This module generates the following exceptions (be sure to look into
391             the sub-modules for other exceptions):
392              
393             =over
394              
395             =item C<< no comic specified >>
396              
397             you must provide a C<comic> parameter to the L</new> constructor method.
398              
399             =item C<< comic %s not available >>
400              
401             sorry, it's not your lucky day. Try to double-check the comic name, and
402             to see if it matches the name of the comic in a specific site.
403              
404             You can use L</get_comics_list> to get a list of all supported comics.
405              
406             =item C<< no plugin for '%s' >>
407              
408             in the L</new> method, you tried to ask for a specific plugin, but it
409             is not supported.
410              
411             =back
412              
413             =head2 Warnings
414              
415             In some cases, exceptions thrown by a particular plugin could be
416             catched and given as warnings. For example, in the search for a plugin
417             capable of providing a specific comic, an error in a plugin is ignored,
418             in the hope that some other plugin will be able to take care of the
419             comic itself.
420              
421              
422             =head1 CONFIGURATION AND ENVIRONMENT
423              
424             WWW::Comix requires no configuration files or environment variables.
425              
426              
427             =head1 DEPENDENCIES
428              
429             The dependencies here are for the general WWW::Comix system:
430              
431             =over
432              
433             =item *
434              
435             HTML::Entities
436              
437             =item *
438              
439             HTML::LinkExtor
440              
441             =item *
442              
443             Module::Pluggable
444              
445             =item *
446              
447             Moose
448              
449             =item *
450              
451             Moose::Policy
452              
453             =item *
454              
455             Path::Class
456              
457             =item *
458              
459             Readonly
460              
461             =item *
462              
463             URI
464              
465             =item *
466              
467             version
468              
469             =item *
470              
471             WWW::Mechanize
472              
473             =back
474              
475             =head1 INCOMPATIBILITIES
476              
477             None reported.
478              
479              
480             =head1 BUGS AND LIMITATIONS
481              
482             No bugs have been reported.
483              
484             Please report any bugs or feature requests through http://rt.cpan.org/
485              
486              
487             =head1 AUTHOR
488              
489             Flavio Poletti C<< <flavio [at] polettix [dot] it> >>
490              
491              
492             =head1 LICENCE AND COPYRIGHT
493              
494             Copyright (c) 2008, Flavio Poletti C<< <flavio [at] polettix [dot] it> >>. All rights reserved.
495              
496             This module is free software; you can redistribute it and/or
497             modify it under the same terms as Perl 5.8.x itself. See L<perlartistic>
498             and L<perlgpl>.
499              
500             Questo modulo è software libero: potete ridistribuirlo e/o
501             modificarlo negli stessi termini di Perl 5.8.x stesso. Vedete anche
502             L<perlartistic> e L<perlgpl>.
503              
504             =head1 DISCLAIMER OF WARRANTY
505              
506             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
507             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
508             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
509             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
510             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
511             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
512             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
513             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
514             NECESSARY SERVICING, REPAIR, OR CORRECTION.
515              
516             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
517             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
518             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
519             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
520             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
521             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
522             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
523             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
524             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
525             SUCH DAMAGES.
526              
527             =head1 NEGAZIONE DELLA GARANZIA
528              
529             Poiché questo software viene dato con una licenza gratuita, non
530             c'è alcuna garanzia associata ad esso, ai fini e per quanto permesso
531             dalle leggi applicabili. A meno di quanto possa essere specificato
532             altrove, il proprietario e detentore del copyright fornisce questo
533             software "così com'è" senza garanzia di alcun tipo, sia essa espressa
534             o implicita, includendo fra l'altro (senza però limitarsi a questo)
535             eventuali garanzie implicite di commerciabilità e adeguatezza per
536             uno scopo particolare. L'intero rischio riguardo alla qualità ed
537             alle prestazioni di questo software rimane a voi. Se il software
538             dovesse dimostrarsi difettoso, vi assumete tutte le responsabilità
539             ed i costi per tutti i necessari servizi, riparazioni o correzioni.
540              
541             In nessun caso, a meno che ciò non sia richiesto dalle leggi vigenti
542             o sia regolato da un accordo scritto, alcuno dei detentori del diritto
543             di copyright, o qualunque altra parte che possa modificare, o redistribuire
544             questo software così come consentito dalla licenza di cui sopra, potrà
545             essere considerato responsabile nei vostri confronti per danni, ivi
546             inclusi danni generali, speciali, incidentali o conseguenziali, derivanti
547             dall'utilizzo o dall'incapacità di utilizzo di questo software. Ciò
548             include, a puro titolo di esempio e senza limitarsi ad essi, la perdita
549             di dati, l'alterazione involontaria o indesiderata di dati, le perdite
550             sostenute da voi o da terze parti o un fallimento del software ad
551             operare con un qualsivoglia altro software. Tale negazione di garanzia
552             rimane in essere anche se i dententori del copyright, o qualsiasi altra
553             parte, è stata avvisata della possibilità di tali danneggiamenti.
554              
555             Se decidete di utilizzare questo software, lo fate a vostro rischio
556             e pericolo. Se pensate che i termini di questa negazione di garanzia
557             non si confacciano alle vostre esigenze, o al vostro modo di
558             considerare un software, o ancora al modo in cui avete sempre trattato
559             software di terze parti, non usatelo. Se lo usate, accettate espressamente
560             questa negazione di garanzia e la piena responsabilità per qualsiasi
561             tipo di danno, di qualsiasi natura, possa derivarne.
562              
563             =cut