File Coverage

blib/lib/Tie/DuckDuckGo.pm
Criterion Covered Total %
statement 55 70 78.5
branch 15 22 68.1
condition n/a
subroutine 15 21 71.4
pod 0 2 0.0
total 85 115 73.9


line stmt bran cond sub pod time code
1             package Tie::DuckDuckGo;
2              
3 1     1   932 use 5.008_005;
  1         4  
  1         41  
4              
5 1     1   5 use strict;
  1         2  
  1         41  
6 1     1   6 use warnings;
  1         1  
  1         54  
7              
8             our $VERSION = '0.02';
9              
10 1     1   960 use URI;
  1         5361  
  1         34  
11 1     1   1062 use HTML::TreeBuilder::XPath;
  1         85483  
  1         16  
12 1     1   1288 use HTTP::Tiny;
  1         50420  
  1         45  
13 1     1   12 use Carp ();
  1         2  
  1         652  
14              
15             # when tying, call the constructor with data type name
16 1     1   6 sub TIEARRAY { shift->new('array', @_) }
17 1     1   6 sub TIEHASH { shift->new('hash', @_) }
18 1     1   902 sub TIESCALAR { shift->new('scalar', @_) }
19              
20             sub new {
21 3     3 0 8 my ($class, $type, $query) = @_;
22              
23             # keep track of tied data type
24 3         12 my $self = bless({type => $type}, $class);
25              
26             # for now i'm using a key _tie_ddg,
27             # but maybe this can be simplified
28 3 100       16 if ($type eq 'array') {
    100          
29 1         4 $self->do_search('_tie_ddg', $query);
30             }
31             elsif ($type eq 'hash') {
32 1         4 $self->do_search($query, $query);
33             }
34             else {
35             ## $scalar = 1
36 1         4 $self->do_search('_tie_ddg', $query, 1);
37             }
38              
39 3         35 $self;
40             }
41              
42             sub do_search {
43 3     3 0 7 my ($self, $store_as, $query, $scalar) = @_;
44 3 50       11 return unless defined $query;
45              
46             # do search
47 3         22 my $uri = URI->new('https://duckduckgo.com/html/');
48 3         11851 $uri->query_form({ q => $query });
49              
50 3         472 my $resp = HTTP::Tiny->new->get($uri->as_string);
51 3         1011539 my $tree = HTML::TreeBuilder::XPath->new_from_content($resp->{content});
52              
53             # build results
54 3         523278 my @results = $tree->findnodes(
55             './/div[@id="content"]//div[@id="links"]
56             //div[@class =~ /\bresults_links\b/]
57             //div[@class =~ /\blinks_main\b/]'
58             );
59              
60 3         1131550 my @save;
61 3         11 for my $result (@results) {
62 45 50       189 my ($link) = $result->findnodes('.//a[@class="large"]') or next;
63 45         107358 my ($snippet) = $result->findvalues('.//div[@class="snippet"]');
64              
65 45         138033 push @save,
66             { url => $link->attr('href'),
67             title => $link->as_text,
68             snippet => $snippet,
69             };
70 45 100       2244 last if $scalar; # only save one result for scalar type
71             }
72              
73 3 100       1945 $self->{data}{$store_as} = $scalar ? [$save[0]] : \@save;
74             }
75              
76             sub FETCH {
77 23     23   41 my ($self, $index) = @_;
78 23 100       50 $index = 0 unless defined $index;
79              
80             # if we're accessing a hash, we may have not done the search already
81 23 50       55 if ($self->{type} eq 'hash') {
82 0 0       0 $self->do_search($index, $index) unless exists $self->{data}{$index};
83              
84 0         0 return $self->{data}{$index};
85             }
86              
87 23         100 $self->{data}{_tie_ddg}[$index];
88             }
89              
90             sub FETCHSIZE {
91 2     2   15 my $self = shift;
92 2         4 scalar @{ $self->{data}{_tie_ddg} };
  2         32  
93             }
94              
95 0     0   0 sub SHIFT { shift @{shift->{data}{_tie_ddg}} }
  0         0  
96 0     0   0 sub POP { pop @{shift->{data}{_tie_ddg}} }
  0         0  
97              
98             sub SPLICE {
99 0     0   0 my ($self, $offset, $limit) = @_;
100              
101             # oops, I don't think we want to write anything to the list
102 0 0       0 if (@_ > 3) {
103 0         0 Carp::carp q,Can't replace search results with list!,;
104 0         0 return;
105             }
106              
107 0         0 splice(@{$self->{data}{_tie_ddg}}, $offset, $limit);
  0         0  
108             }
109              
110             sub EXISTS {
111 2     2   14 my ($self, $index) = @_;
112 2 100       17 return exists($self->{data}{$index}) if $self->{type} eq 'hash';
113 1         11 exists $self->{data}{_tie_ddg}[$index];
114             }
115              
116 0     0     sub STORE { Carp::carp q,Can't store search results to hash!, }
117 0     0     sub PUSH { Carp::carp q,Can't push search results to array!, }
118 0     0     sub UNSHIFT { Carp::carp q,Can't unshift search results to array!, }
119              
120             1;
121             __END__