| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Net::HTTP::Spore; | 
| 2 |  |  |  |  |  |  | $Net::HTTP::Spore::VERSION = '0.07'; | 
| 3 |  |  |  |  |  |  | # ABSTRACT: SPORE client | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 21 |  |  | 21 |  | 1168569 | use Moose; | 
|  | 21 |  |  |  |  | 7204415 |  | 
|  | 21 |  |  |  |  | 154 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 21 |  |  | 21 |  | 148783 | use IO::All; | 
|  | 21 |  |  |  |  | 177605 |  | 
|  | 21 |  |  |  |  | 197 |  | 
| 8 | 21 |  |  | 21 |  | 8207 | use JSON; | 
|  | 21 |  |  |  |  | 111064 |  | 
|  | 21 |  |  |  |  | 136 |  | 
| 9 | 21 |  |  | 21 |  | 2719 | use Carp; | 
|  | 21 |  |  |  |  | 40 |  | 
|  | 21 |  |  |  |  | 1069 |  | 
| 10 | 21 |  |  | 21 |  | 126 | use Try::Tiny; | 
|  | 21 |  |  |  |  | 39 |  | 
|  | 21 |  |  |  |  | 875 |  | 
| 11 | 21 |  |  | 21 |  | 112 | use Scalar::Util; | 
|  | 21 |  |  |  |  | 40 |  | 
|  | 21 |  |  |  |  | 647 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 | 21 |  |  | 21 |  | 6371 | use Net::HTTP::Spore::Core; | 
|  | 21 |  |  |  |  | 70 |  | 
|  | 21 |  |  |  |  | 14273 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | # XXX should we let the possibility to override this super class, or add | 
| 16 |  |  |  |  |  |  | # another superclasses? | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | sub new_from_string { | 
| 19 | 30 |  |  | 30 | 1 | 10124 | my ($class, $string, %args) = @_; | 
| 20 |  |  |  |  |  |  |  | 
| 21 | 30 |  |  |  |  | 333 | my $spore_class = | 
| 22 |  |  |  |  |  |  | Class::MOP::Class->create_anon_class( | 
| 23 |  |  |  |  |  |  | superclasses => ['Net::HTTP::Spore::Core'] ); | 
| 24 |  |  |  |  |  |  |  | 
| 25 | 30 |  |  |  |  | 104330 | my $spore_object = $class->_attach_spec_to_class($string, \%args, $spore_class); | 
| 26 |  |  |  |  |  |  |  | 
| 27 | 27 |  |  |  |  | 306 | return $spore_object; | 
| 28 |  |  |  |  |  |  | } | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | sub new_from_strings { | 
| 31 | 5 |  |  | 5 | 0 | 2062 | my $class = shift; | 
| 32 |  |  |  |  |  |  |  | 
| 33 | 5 |  |  |  |  | 8 | my $opts; | 
| 34 | 5 | 100 |  |  |  | 24 | if (ref ($_[-1]) eq 'HASH') { | 
| 35 | 3 |  |  |  |  | 10 | $opts = pop @_; | 
| 36 |  |  |  |  |  |  | } | 
| 37 | 5 |  |  |  |  | 16 | my @strings = @_; | 
| 38 |  |  |  |  |  |  |  | 
| 39 | 5 |  |  |  |  | 37 | my $spore_class = | 
| 40 |  |  |  |  |  |  | Class::MOP::Class->create_anon_class( | 
| 41 |  |  |  |  |  |  | superclasses => ['Net::HTTP::Spore::Core'] ); | 
| 42 |  |  |  |  |  |  |  | 
| 43 | 5 |  |  |  |  | 15005 | my $spore_object = undef; | 
| 44 | 5 |  |  |  |  | 19 | foreach my $string (@strings) { | 
| 45 | 8 |  |  |  |  | 41 | $spore_object = $class->_attach_spec_to_class($string, $opts, $spore_class, $spore_object); | 
| 46 |  |  |  |  |  |  | } | 
| 47 | 3 |  |  |  |  | 43 | return $spore_object; | 
| 48 |  |  |  |  |  |  | } | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | sub new_from_spec { | 
| 51 | 22 |  |  | 22 | 1 | 21268 | my ( $class, $spec_file, %args ) = @_; | 
| 52 |  |  |  |  |  |  |  | 
| 53 | 22 | 100 |  |  |  | 115 | Carp::confess("specification file is missing") unless $spec_file; | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 21 |  |  |  |  | 89 | my $content = _read_spec($spec_file); | 
| 56 |  |  |  |  |  |  |  | 
| 57 | 20 |  |  |  |  | 571 | $class->new_from_string( $content, %args ); | 
| 58 |  |  |  |  |  |  | } | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | sub new_from_specs { | 
| 61 | 2 |  |  | 2 | 0 | 2114 | my $class = shift; | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 2 |  |  |  |  | 4 | my $opts; | 
| 64 | 2 | 100 |  |  |  | 9 | if (ref ($_[-1]) eq 'HASH') { | 
| 65 | 1 |  |  |  |  | 3 | $opts = pop @_; | 
| 66 |  |  |  |  |  |  | } | 
| 67 | 2 |  |  |  |  | 77 | my @specs = @_; | 
| 68 |  |  |  |  |  |  |  | 
| 69 | 2 |  |  |  |  | 5 | my @strings; | 
| 70 | 2 |  |  |  |  | 6 | foreach my $spec (@specs) { | 
| 71 | 4 |  |  |  |  | 45 | push @strings,_read_spec($spec); | 
| 72 |  |  |  |  |  |  | } | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 2 |  |  |  |  | 39 | $class->new_from_strings(@strings, $opts); | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | sub _attach_spec_to_class { | 
| 78 | 38 |  |  | 38 |  | 175 | my ( $class, $string, $opts, $spore_class, $object ) = @_; | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 38 |  |  |  |  | 81 | my $spec; | 
| 81 |  |  |  |  |  |  | try { | 
| 82 | 38 |  |  | 38 |  | 3642 | $spec = JSON::decode_json($string); | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  | catch { | 
| 85 | 1 |  |  | 1 |  | 40 | Carp::confess( "unable to parse JSON spec: " . $_ ); | 
| 86 | 38 |  |  |  |  | 399 | }; | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | try { | 
| 89 | 37 |  | 100 | 37 |  | 1752 | $opts->{base_url} ||= $spec->{base_url}; | 
| 90 | 37 | 100 |  |  |  | 202 | die "base_url is missing!" if !$opts->{base_url}; | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 35 | 50 |  |  |  | 139 | if ( $spec->{formats} ) { | 
| 93 | 0 |  |  |  |  | 0 | $opts->{formats} = $spec->{formats}; | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  |  | 
| 96 | 35 | 100 |  |  |  | 145 | if ( $spec->{authentication} ) { | 
| 97 | 1 |  |  |  |  | 16 | $opts->{authentication} = $spec->{authentication}; | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 35 | 100 |  |  |  | 161 | if ( !$object ) { | 
| 101 | 32 |  |  |  |  | 255 | $object = $spore_class->new_object(%$opts); | 
| 102 |  |  |  |  |  |  | } | 
| 103 | 35 |  |  |  |  | 419 | $object = $class->_add_methods( $object, $spec->{methods} ); | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  | catch { | 
| 106 | 4 |  |  | 4 |  | 85228 | Carp::confess( "unable to create new Net::HTTP::Spore object: " . $_ ); | 
| 107 | 37 |  |  |  |  | 878 | }; | 
| 108 |  |  |  |  |  |  |  | 
| 109 | 33 |  |  |  |  | 1160 | return $object; | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | sub _read_spec { | 
| 113 | 25 |  |  | 25 |  | 63 | my $spec_file = shift; | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 25 |  |  |  |  | 49 | my $content; | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 25 | 50 |  |  |  | 112 | if ( $spec_file =~ m!^http(s)?://! ) { | 
| 118 | 0 |  |  |  |  | 0 | my $uri = URI->new($spec_file); | 
| 119 | 0 |  |  |  |  | 0 | my $req = HTTP::Request->new( GET => $spec_file ); | 
| 120 | 0 |  |  |  |  | 0 | my $ua  = LWP::UserAgent->new(); | 
| 121 | 0 |  |  |  |  | 0 | my $res = $ua->request($req); | 
| 122 | 0 | 0 |  |  |  | 0 | unless( $res->is_success ) { | 
| 123 | 0 |  |  |  |  | 0 | my $status = $res->status_line; | 
| 124 | 0 |  |  |  |  | 0 | Carp::confess("Unabled to fetch $spec_file ($status)"); | 
| 125 |  |  |  |  |  |  | } | 
| 126 | 0 |  |  |  |  | 0 | $content = $res->content; | 
| 127 |  |  |  |  |  |  | } | 
| 128 |  |  |  |  |  |  | else { | 
| 129 | 25 | 100 |  |  |  | 532 | unless ( -f $spec_file ) { | 
| 130 | 1 |  |  |  |  | 19 | Carp::confess("$spec_file does not exists"); | 
| 131 |  |  |  |  |  |  | } | 
| 132 | 24 |  |  |  |  | 149 | $content < io($spec_file); | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 24 |  |  |  |  | 111548 | return $content; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | sub _add_methods { | 
| 139 | 35 |  |  | 35 |  | 122 | my ($class, $spore, $methods_spec) = @_; | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 35 |  |  |  |  | 163 | foreach my $method_name (keys %$methods_spec) { | 
| 142 |  |  |  |  |  |  | $spore->meta->add_spore_method($method_name, | 
| 143 | 122 |  |  |  |  | 18555 | %{$methods_spec->{$method_name}}); | 
|  | 122 |  |  |  |  | 2791 |  | 
| 144 |  |  |  |  |  |  | } | 
| 145 | 33 |  |  |  |  | 6707 | $spore; | 
| 146 |  |  |  |  |  |  | } | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | 1; | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | __END__ | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | =pod | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | =encoding UTF-8 | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | =head1 NAME | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | Net::HTTP::Spore - SPORE client | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | =head1 VERSION | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | version 0.07 | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | my $client = Net::HTTP::Spore->new_from_spec('twitter.json'); | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | # from JSON specification string | 
| 169 |  |  |  |  |  |  | my $client = Net::HTTP::Spore->new_from_string($json); | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | # for identica | 
| 172 |  |  |  |  |  |  | my $client = Net::HTTP::Spore->new_from_spec('twitter.json', base_url => 'http://identi.ca/com/api'); | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | $client->enable('Format::JSON'); | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | my $timeline = $client->public_timeline(format => 'json'); | 
| 177 |  |  |  |  |  |  | my $tweets = $timeline->body; | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | foreach my $tweet (@$tweets) { | 
| 180 |  |  |  |  |  |  | print $tweet->{user}->{screen_name}. " says ".$tweet->{text}."\n"; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | my $friends_timeline = $client->friends_timeline(format => 'json'); | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | This module is an implementation of the SPORE specification. | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | To use this client, you need to use or to write a SPORE specification of an | 
| 190 |  |  |  |  |  |  | API.  A description of the SPORE specification format is available at | 
| 191 |  |  |  |  |  |  | L<http://github.com/SPORE/specifications/blob/master/spore_description.pod> | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | Some specifications for well-known services are available | 
| 194 |  |  |  |  |  |  | L<http://github.com/SPORE/api-description>. | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | =head2 CLIENT CREATION | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | First you need to create a client. This can be done using two methods, | 
| 199 |  |  |  |  |  |  | B<new_from_spec> and B<new_from_string>. The client will read the specification | 
| 200 |  |  |  |  |  |  | file to create the appropriate methods to interact with the API. | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | =head2 MIDDLEWARES | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | It's possible to activate some middlewares to extend the usage of the client. | 
| 205 |  |  |  |  |  |  | If you're using an API that discuss in JSON, you can enable the middleware | 
| 206 |  |  |  |  |  |  | L<Net::HTTP::Spore::Middleware::JSON>. | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | $client->enable('Format::JSON'); | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | or only on some path | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | $client->enable_if(sub{$_->[0]->path =~ m!/path/to/json/stuff!}, 'Format::JSON'); | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | For very simple middlewares, you can simply pass in an anonymous function | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | $client->enable( sub { my $request = shift; ... } ); | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | =head2 METHODS | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | =over 4 | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | =item new_from_spec($specification_file, %args) | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | Create and return a L<Net::HTTP::Spore::Core> object, with methods generated | 
| 225 |  |  |  |  |  |  | from the specification file. The specification file can either be a file on | 
| 226 |  |  |  |  |  |  | disk or a remote URL. | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | =item new_from_string($specification_string, %args) | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | Create and return a L<Net::HTTP::Spore::Core> object, with methods | 
| 231 |  |  |  |  |  |  | generated from a JSON specification string. | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | =back | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | =head2 TRACING | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | L<Net::HTTP::Spore> provides a way to trace what's going on when doing a | 
| 238 |  |  |  |  |  |  | request. | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | =head3 Enabling Trace | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | You can enable tracing using the environment variable B<SPORE_TRACE>. You can | 
| 243 |  |  |  |  |  |  | also enable tracing at construct time by adding B<trace =E<gt> 1> when calling | 
| 244 |  |  |  |  |  |  | B<new_from_spec>. | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | =head3 Trace Output | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | By default output will be directed to B<STDERR>. You can specify another | 
| 249 |  |  |  |  |  |  | default output: | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | SPORE_TRACE=1=log.txt | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | or | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | ->new_from_spec('spec.json', trace => '1=log.txt'); | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | =head1 AUTHORS | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | =over 4 | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | =item * | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | Franck Cuny <franck.cuny@gmail.com> | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | =item * | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | Ash Berlin <ash@cpan.org> | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | =item * | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | Ahmad Fatoum <athreef@cpan.org> | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | =back | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | This software is copyright (c) 2012 by Linkfluence. | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 280 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | =cut |