File Coverage

blib/lib/WWW/SchneierFacts.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package WWW::SchneierFacts;
4 1     1   30092 use Moose;
  0            
  0            
5              
6             use Carp qw(croak);
7             use Web::Scraper;
8             use URI;
9              
10             use WWW::SchneierFacts::Fact;
11              
12             use namespace::clean -except => [qw(meta)];
13              
14             our $VERSION = "0.02";
15              
16             has cache => (
17             isa => "HashRef[WWW::SchneierFacts::Fact]",
18             is => "rw",
19             default => sub { {} },
20             );
21              
22             has top_facts_uri => (
23             isa => "URI",
24             is => "rw",
25             default => sub { URI->new("http://geekz.co.uk/schneierfacts/facts/top") },
26             );
27              
28             has random_fact_uri => (
29             isa => "URI",
30             is => "rw",
31             default => sub { URI->new("http://geekz.co.uk/schneierfacts/") },
32             );
33              
34             has fact_base_uri => (
35             isa => "URI",
36             is => "rw",
37             default => sub { URI->new("http://geekz.co.uk/schneierfacts/fact/") },
38             );
39              
40             sub fact {
41             my ( $self, @args ) = @_;
42              
43             unless ( @args ) {
44             return $self->random_fact;
45             } else {
46             if ( @args == 1 ) {
47             unshift @args, ( ref $args[0] ? "link" : "id" );
48             }
49              
50             return $self->new_fact(@args);
51             }
52             }
53              
54             sub top_facts {
55             my $self = shift;
56             $self->new_facts( $self->scrape( top_facts => $self->top_facts_uri ) );
57             }
58              
59             sub random_fact {
60             my $self = shift;
61             $self->new_fact(%{ $self->scrape( fact => $self->random_fact_uri ) });
62             }
63              
64             sub scrape {
65             my ( $self, $what, $uri ) = @_;
66              
67             my $scraper = $self->_scraper($what);
68              
69             my $ret = $scraper->scrape($uri) or croak "$uri did not contain the desired data";
70              
71             my @ret = ref $ret eq 'ARRAY' ? @$ret : $ret;
72              
73             return $self->scrub( wantarray ? @ret : $ret[0] );
74             }
75              
76             sub _scraper {
77             my ( $self, $name ) = @_;
78             my $method = $name . "_scraper";
79             croak "Dunno how to scrape $name" unless $self->can($method);
80             return $self->$method;
81             }
82              
83             sub scrub {
84             my ( $self, @blah ) = @_;
85              
86             foreach my $entry ( @blah ) {
87             for ( grep { not ref } values %$entry ) {
88             s/^\s+//;
89             s/\s+$//;
90             }
91              
92             $entry->{author} =~ s/^submitted by\s*//i;
93             delete $entry->{author} if lc($entry->{author}) eq 'anonymous';
94             }
95              
96             return wantarray ? @blah : $blah[0];
97             }
98              
99             sub new_facts {
100             my ( $self, @blah ) = @_;
101             map { $self->new_fact(%$_) } @blah;
102             }
103              
104             sub new_fact {
105             my ( $self, %args ) = @_;
106              
107             my $id = $args{id};
108             my $link = $args{link};
109             my $cache = $self->cache;
110              
111             if ( my $fact = ( ( $id && $cache->{$id} ) || ( $link && $cache->{$link} ) ) ) {
112             return $fact;
113             } else {
114             my $fact = $self->fact_class->new( %args, db => $self );
115             return $cache->{$fact->link} = $cache->{$fact->id} = $fact;
116             }
117             }
118              
119             has fact_class => (
120             isa => "ClassName",
121             is => "rw",
122             default => "WWW::SchneierFacts::Fact",
123             );
124              
125             has [map { $_ . "_scraper" } qw(top_facts fact_list fact)] => (
126             isa => "Object",
127             is => "ro",
128             lazy_build => 1,
129             );
130              
131             sub _build_fact_scraper {
132             scraper {
133             process 'div#content' => content => scraper {
134             process 'p.fact', fact => 'TEXT';
135             process 'p.author', author => 'TEXT';
136             process '//p[@class="actionbar"]/a[contains(text(), "permalink")]', link => '@href';
137             };
138             result 'content';
139             };
140             }
141              
142             sub _build_top_facts_scraper {
143             scraper {
144             process 'div#content' => content => scraper {
145             process 'p.top-fact', 'facts[]' => scraper {
146             process 'a', fact => 'TEXT', author => '@title', link => '@href';
147             };
148             result 'facts';
149             };
150             result 'content';
151             };
152             }
153              
154             sub _build_fact_list_scraper {
155             scraper {
156             process 'div#content' => content => scraper {
157             process 'ul.fact-list li', 'facts[]' => scraper {
158             process 'a', fact => 'TEXT', author => '@title', link => '@href';
159             };
160             result 'facts';
161             };
162             result 'content';
163             };
164             }
165              
166             __PACKAGE__
167              
168             __END__
169              
170             =pod
171              
172             =head1 NAME
173              
174             WWW::SchneierFacts - API for retrieving facts about Bruce Schneier
175              
176             =head1 SYNOPSIS
177              
178             use WWW::SchneierFacts;
179              
180             my $db = WWW::SchneierFacts->new;
181              
182             foreach my $fact ( $db->top_facts ) {
183             print "$fact\n",
184             ( $fact->author ? ( " --", $fact->author, "\n" ) : () ),
185             "\n";
186             }
187              
188             =head1 DESCRIPTION
189              
190             Bruce Schneier is the Chuck Norris of cryptography.
191              
192             =head1 METHODS
193              
194             =over 4
195              
196             =item fact $id
197              
198             =item fact $uri
199              
200             =item fact
201              
202             Return a fact with the ID C<id>, or if a L<URI> object is provided, at the link.
203              
204             If no arguments are given a random fact will be fetched.
205              
206             Returns a L<WWW::SchneierFacts::Fact> object.
207              
208             =item top_facts
209              
210             Get the top facts.
211              
212             =back
213              
214             =head1 VERSION CONTROL
215              
216             This module is maintained using Darcs. You can get the latest version from
217             L<http://nothingmuch.woobling.org/code>, and use C<darcs send> to commit
218             changes.
219              
220             =head1 AUTHOR
221              
222             Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
223              
224             =head1 COPYRIGHT
225              
226             Copyright (c) 2008 Yuval Kogman. All rights reserved
227             This program is free software; you can redistribute
228             it and/or modify it under the same terms as Perl itself.
229              
230             =cut