PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/BitRest.pm

https://bitbucket.org/vvp/bit-rest
Perl | 444 lines | 314 code | 121 blank | 9 comment | 45 complexity | 4b04e819282e63626340f64590cd15a7 MD5 | raw file
  1. package BitRest;
  2. # vim: set ft=perl et ts=4 sw=4:
  3. =head1 NAME
  4. BitRest - Bitbucket REST resources in Perl objects
  5. =head1 VERSION
  6. Version 0.43
  7. =cut
  8. our $VERSION = '0.43';
  9. =head1 SYNOPSIS
  10. use BitRest;
  11. $cmd = BitRest->alias($cmd);
  12. BitRest->resource($res_name);
  13. my $res = BitRest->new($res_name);
  14. $res->id($id);
  15. $res->repo($repo);
  16. $res->color($color);
  17. $res->request($cmd, $user, $pass);
  18. =head1 DESCRIPTION
  19. B<BitRest> is a Perl module for convinient interface to Bitbucket REST APIs.
  20. Implemented resources:
  21. =over 4
  22. =item * B<Issues> (L<BitRest::Issue>)
  23. =back
  24. This module is used in B<bit-rest> script.
  25. Run C<bit-rest -M> to see its manual.
  26. =cut
  27. use strict;
  28. use warnings;
  29. use feature 'switch';
  30. use Getopt::Long qw(:config no_ignore_case);
  31. use URI::Escape 'uri_escape';
  32. use WWW::Curl::Easy;
  33. use JSON 'decode_json';
  34. use Pod::Find 'pod_where';
  35. use Pod::Usage;
  36. use Data::Dumper;
  37. # Variables
  38. our $API_URL = 'https://api.bitbucket.org/1.0/';
  39. # implicit specification of the implemented resources
  40. our @RESOURCE = qw(issue);
  41. # aliases for commands
  42. our %ALIAS = (
  43. get => 'get',
  44. list => 'get',
  45. view => 'get',
  46. add => 'add',
  47. new => 'add',
  48. put => 'put',
  49. update => 'put',
  50. del => 'del',
  51. delete => 'del'
  52. );
  53. # error message
  54. our $errstr = '';
  55. #### Subroutines
  56. sub url_encode {
  57. my ( $p, $s ) = ( shift, '' );
  58. while ( my ( $k, $v ) = each(%$p) ) {
  59. if ( ref $v eq 'ARRAY' ) {
  60. $s .= uri_escape($k) . '=' . uri_escape($_) . '&' for @$v;
  61. }
  62. else {
  63. $s .= uri_escape($k) . '=' . uri_escape($v) . '&';
  64. }
  65. }
  66. return $s && substr $s, 0, -1;
  67. }
  68. =head1 METHODS
  69. =over 4
  70. =item B<BitRest>->B<new>(I<resource_name>)
  71. Initialize the specified resource object and return its blessed reference.
  72. If there are errors return B<undef>.
  73. =cut
  74. sub new {
  75. my ( $class, $res ) = @_;
  76. if ( !( $res ~~ @RESOURCE ) ) {
  77. $errstr = "Resource '$res' is not implemented!";
  78. return undef;
  79. }
  80. $class .= "::\u$res";
  81. eval "require $class";
  82. if ($@) {
  83. $errstr = "Class '$class' cannot be loaded!\n$@";
  84. return undef;
  85. }
  86. return eval $class . "->new";
  87. }
  88. =item B<BitRest>->B<init>()
  89. Initialization of the resource object.
  90. Used only by successors in their B<new> method.
  91. sub new {
  92. my $class = shift;
  93. my $self = $class->SUPER::init;
  94. # initialize $self fields
  95. return bless $self, $class;
  96. }
  97. =cut
  98. sub init {
  99. return bless {
  100. ACTIONS => [],
  101. OPTIONS => [],
  102. LINK => '',
  103. REPO => '',
  104. ID => 0,
  105. COLOR => 'auto',
  106. FMT_SHORT => '',
  107. FMT_LONG => ''
  108. }, shift;
  109. }
  110. =item B<BitRest>->B<help>(I<resource_name>)
  111. Display POD for the specified BitRest resource module.
  112. =cut
  113. sub help {
  114. my ( $class, $res ) = @_;
  115. $res && $res ~~ @RESOURCE or return;
  116. $class .= "::\u$res";
  117. my $f = pod_where( { -inc => 1, -verbose => 0 }, $class );
  118. pod2usage( -exitval => 0, -verbose => 3, -input => $f ) if $f;
  119. }
  120. =item B<request>(I<command>, I<options>, I<owner>, I<user>, I<password>)
  121. Main method of the BitRest object which makes request
  122. to the Bitbucket server using the REST API.
  123. =over 4
  124. =item I<command>
  125. Command to apply to the resource(s).
  126. =item I<options>
  127. Reference to a hash with command line options.
  128. =item I<owner>
  129. User who owns the repository.
  130. =item I<user> and I<password>
  131. User name and password for authentication.
  132. =back
  133. Return value is the I<HTTP status code> if succeed.
  134. Prior to using this method the caller should set B<REPO> and B<ID> fields
  135. using corresponding methods.
  136. =cut
  137. sub request {
  138. my ( $self, $cmd, $opt, $owner, $user, $pass ) = @_;
  139. my $curl = WWW::Curl::Easy->new;
  140. my ( $json, $head, $r );
  141. $r = ref $self->{OPTIONS};
  142. GetOptions( $opt, (
  143. $r eq 'ARRAY' ? @{ $self->{OPTIONS} } : (
  144. $r eq 'HASH' ? %{ $self->{OPTIONS} } : () )));
  145. $self->options($opt) if $self->can('options');
  146. my $url = $self->url($owner) || return undef;
  147. my $data = url_encode($opt);
  148. # prepare CURL
  149. given ($cmd) {
  150. when ('get') {
  151. $curl->setopt( CURLOPT_HTTPGET, 1 );
  152. $curl->setopt( CURLOPT_WRITEDATA, \$json );
  153. $url .= "?$data" if $data;
  154. }
  155. when ('add') {
  156. $curl->setopt( CURLOPT_POST, 1 );
  157. $curl->setopt( CURLOPT_POSTFIELDS, $data );
  158. $curl->setopt( CURLOPT_POSTFIELDSIZE, length $data );
  159. }
  160. when ('put') {
  161. if ( !$self->{ID} ) {
  162. $errstr = "Cannot update entry without id!";
  163. return undef;
  164. }
  165. $curl->setopt( CURLOPT_UPLOAD, 1 );
  166. $curl->setopt( CURLOPT_READDATA, $data );
  167. $curl->setopt( CURLOPT_INFILESIZE, length $data );
  168. }
  169. when ('del') {
  170. if ( !$self->{ID} ) {
  171. $errstr = "Cannot delete entry without id!";
  172. return undef;
  173. }
  174. $curl->setopt( CURLOPT_CUSTOMREQUEST, 'DELETE' );
  175. }
  176. }
  177. # Send request and get response
  178. $curl->setopt( CURLOPT_URL, $url );
  179. $curl->setopt( CURLOPT_USERNAME, $user ) if $user;
  180. $curl->setopt( CURLOPT_PASSWORD, $pass ) if $pass;
  181. $curl->setopt( CURLOPT_WRITEHEADER, \$head );
  182. if ( $r = $curl->perform ) {
  183. $errstr = "curl $r: " . $curl->strerror($r) . "\n" . $curl->errbuf;
  184. return undef;
  185. }
  186. $r = $curl->getinfo(CURLINFO_RESPONSE_CODE);
  187. $errstr = ( split /\n/, $head )[0] =~ s/^[^\s]+\s//r;
  188. $r >= 200 && $r < 300 or return undef;
  189. $self->print( decode_json $json) if $cmd eq 'get';
  190. return $r;
  191. }
  192. =item B<errstr>
  193. Return B<errstr> - error message set if any error occurs in this module.
  194. Also error message can be retrieved from B<$BitRest::errstr> variable.
  195. =cut
  196. sub errstr {
  197. return $errstr;
  198. }
  199. =item B<resource>([I<res_name>])
  200. Check whether the resource I<res_name> is implemented
  201. or return a list of the implemented resources if no parameters given.
  202. =cut
  203. sub resource {
  204. my $class = shift;
  205. return shift ~~ @RESOURCE if @_;
  206. return @RESOURCE;
  207. }
  208. =item B<alias>([I<command>])
  209. Check whether the I<command> is an alias
  210. or return a hash with aliases if no parameters given.
  211. =cut
  212. sub alias {
  213. my ( $class, $cmd ) = @_;
  214. return $ALIAS{$cmd} if defined $cmd;
  215. return %ALIAS;
  216. }
  217. =item B<color>([I<color>])
  218. Set the B<COLOR> field to I<color> (can be B<auto>, B<always>, or B<never>)
  219. or check whether the coloring should be used if no parameters given.
  220. =cut
  221. sub color {
  222. my $self = shift;
  223. $self->{COLOR} = shift if @_;
  224. return ( $self->{COLOR} eq 'always'
  225. || ( $self->{COLOR} eq 'auto' && -t STDOUT ) ? 1 : 0 );
  226. }
  227. =item B<id>([I<id>])
  228. Set the B<ID> field to I<id> (can be string or integer)
  229. or return current resource id if no parameters given.
  230. =cut
  231. sub id {
  232. my $self = shift;
  233. $self->{ID} = shift if @_;
  234. return $self->{ID};
  235. }
  236. =item B<repo>([I<repo_slug>])
  237. Set the B<REPO> field to I<repo_slug> or return current repo slug
  238. if no parameters given.
  239. =cut
  240. sub repo {
  241. my $self = shift;
  242. $self->{REPO} = shift if @_;
  243. return $self->{REPO};
  244. }
  245. =item B<url>(I<user_name>)
  246. Make URL for HTTP REST request. Used internally.
  247. =cut
  248. sub url {
  249. my ( $self, $user ) = @_;
  250. my ( $url, $id ) = ( $self->{LINK}, $self->{ID} );
  251. if($url ~~ /%r/ && !$self->{REPO}){
  252. $errstr = "Repository name was not specified";
  253. return undef;
  254. }
  255. $url =~ s|%r|$self->{REPO}|g;
  256. $url =~ s|%u|$user|g;
  257. $id = $id ? "$id/" : "";
  258. $url =~ s|%i|$id|g;
  259. return $API_URL . $url;
  260. }
  261. =item B<print>(I<data>, I<resource_name>)
  262. Print resource or list of resources.
  263. I<data> is a reference to a hash with REST resource data.
  264. I<resource_name> is the name of the resource to print.
  265. Used by successors in B<print> method:
  266. sub print {
  267. my($self, $data) = @_;
  268. $self->SUPER::print($data, 'resource_name');
  269. }
  270. =cut
  271. sub print {
  272. my ( $self, $data, $res ) = @_;
  273. if ( $self->{ID} ) {
  274. $self->format($self->{FMT_LONG}, $data)
  275. }
  276. else {
  277. $self->format($self->{FMT_SHORT}, $_) for @{ $data->{$res} }
  278. }
  279. }
  280. =item B<format>(I<format>, I<data>)
  281. Print resource I<data> formatted using I<format> string.
  282. =cut
  283. sub format {
  284. no warnings;
  285. my ( $self, $fmt, $data ) = @_;
  286. my @arg;
  287. my $pat = qr/(%[-+0# ]?(?:[0-9]+)?(?:\.[0-9]+)?)([A-Za-z]{1,2})/o;
  288. $fmt =~ s/$pat/$self->fmt_choice($1, $2, \@arg, $data)/ge;
  289. printf $fmt . "\n", @arg;
  290. }
  291. sub fmt_choice {
  292. return 0;
  293. }
  294. =item B<colored>(I<string>, I<color_codes>)
  295. Return I<string> surrounded by specified I<color codes>
  296. if B<color> returns B<TRUE>.
  297. =cut
  298. sub colored {
  299. my ( $self, $s, $c ) = @_;
  300. return ( $self->color && $c ? "\e[${c}m$s\e[0m" : $s );
  301. }
  302. 1;
  303. __END__
  304. =back
  305. =head1 SEE ALSO
  306. L<bit-rest>, BitRest::*
  307. =head1 SOURCE
  308. The source code repository for BitRest can be found at
  309. L<https://bitbucket.org/vvp/bit-rest>.
  310. =head1 BUGS
  311. See the repository issue tracker at
  312. L<https://bitbucket.org/vvp/bit-rest/issues>
  313. to report and view bugs.
  314. =head1 AUTHOR
  315. vvp <vvp.psu[at]gmail.com>
  316. =head1 LICENSE AND COPYRIGHT
  317. Copyright (c) 2012 vvp (vvp.psu[at]gmail.com).
  318. All rights reserved.
  319. This module is free software; you can redstribute it and/or modify it under
  320. the same terms as Perl itself. See L<perlartistic>. This module is
  321. distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  322. without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
  323. PARTICULAR PURPOSE.