File Coverage

blib/lib/Hatena/Keyword.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Hatena::Keyword;
2 4     4   85339 use strict;
  4         11  
  4         161  
3 4     4   25 use warnings;
  4         9  
  4         140  
4 4     4   24 use base qw(Class::Data::Inheritable Class::Accessor::Fast Class::ErrorHandler);
  4         12  
  4         3749  
5 4     4   30489 use overload '""' => \&as_string, fallback => 1;
  4         9229  
  4         39  
6 4     4   275 use Carp;
  4         8  
  4         326  
7 4     4   3745 use URI;
  4         20891  
  4         126  
8 4     4   4015 use RPC::XML;
  0            
  0            
9             use RPC::XML::Client;
10              
11             our $VERSION = 0.05;
12              
13             my @Fields = qw(refcount word score cname);
14             __PACKAGE__->mk_accessors(@Fields);
15             __PACKAGE__->mk_classdata(rpc_client => RPC::XML::Client->new(
16             URI->new_abs('/xmlrpc', 'http://d.hatena.ne.jp/'),
17             useragent => [ agent => join('/', __PACKAGE__, __PACKAGE__->VERSION) ],
18             ));
19              
20             sub extract {
21             my $class = shift;
22             my $body = shift or croak sprintf 'usage %s->extract($text)', $class;
23             my $args = shift || {};
24             $args->{mode} = 'lite';
25             my $res = $class->_call_rpc_with_cache($body, $args)
26             or $class->error($class->errstr);
27             my @keywords = map { $class->_instance_from_rpcdata($_) }@{$res->{wordlist}};
28             return wantarray ? @keywords : \@keywords;
29             }
30              
31             sub markup_as_html {
32             my $class = shift;
33             my $body = shift or croak sprintf 'usage %s->markup_as_html($text)', $class;
34             my $args = shift || {};
35             $args->{mode} = '';
36             my $res = $class->_call_rpc_with_cache($body, $args)
37             or $class->error($class->errstr);
38             return $res->value;
39             }
40              
41             sub _call_rpc_with_cache {
42             my $class = shift;
43             my ($body, $args) = @_;
44             $body = pack('C0A*', $body); # hacking for utf-8 flag
45             my $cache = delete $args->{cache};
46             return $class->_call_rpc($body, $args) unless ref($cache);
47             croak "cache object must have get and set method."
48             if not $cache->can('get') or not $cache->can('set');
49              
50             require Digest::MD5;
51             require Storable;
52             my $key = sprintf(
53             '%s-%s-%s',
54             $args->{mode} || '',
55             Digest::MD5::md5_hex($body),
56             Digest::MD5::md5_hex(Storable::freeze($args)),
57             );
58             my $res = Storable::thaw($cache->get($key));
59             unless (defined $res) {
60             $res = $class->_call_rpc($body, $args)
61             or return $class->error($class->errstr);
62             $cache->set($key =>Storable::freeze($res));
63             }
64             $res;
65             }
66              
67             sub _call_rpc {
68             my ($class, $body, $args) = @_;
69             my $params = {
70             body => RPC::XML::string->new($body),
71             score => RPC::XML::int->new($args->{score} || 0),
72             mode => RPC::XML::string->new($args->{mode} || ''),
73             cname => defined $args->{cname} ? RPC::XML::array->new(
74             map { RPC::XML::string->new($_) } @{$args->{cname}}
75             ) : undef,
76             a_target => RPC::XML::string->new($args->{a_target} || ''),
77             a_class => RPC::XML::string->new($args->{a_class} || ''),
78             };
79              
80             # For all categories, It doesn't need an undefined cname value.
81             delete $params->{cname} unless defined $params->{cname};
82              
83             my $res = $class->rpc_client->send_request(
84             RPC::XML::request->new('hatena.setkeywordlink', $params),
85             );
86             return ref $res ? $res : $class->error(qq/RPC Error: "$res"/);
87             }
88              
89             sub _instance_from_rpcdata {
90             my ($class, $data) = @_;
91             return $class->new({
92             map {$_ => $data->{$_}->value } @Fields,
93             });
94             }
95              
96             sub jcode {
97             my $self = shift;
98             $self->{_jcode} and return $self->{_jcode};
99             require Jcode;
100             return $self->{_jcode} = Jcode->new($self->as_string, 'utf8');
101             }
102              
103             sub as_string { $_[0]->word }
104              
105             1;
106              
107             __END__