File Coverage

blib/lib/App/AYCABTU.pm
Criterion Covered Total %
statement 21 233 9.0
branch 0 92 0.0
condition 0 32 0.0
subroutine 7 32 21.8
pod 0 12 0.0
total 28 401 6.9


line stmt bran cond sub pod time code
1 1     1   1260 use strict; use warnings;
  1     1   3  
  1         55  
  1         6  
  1         2  
  1         67  
2             package App::AYCABTU;
3             our $VERSION = '0.13';
4              
5 1     1   943 use Mouse;
  1         70922  
  1         5  
6 1     1   1549 use Getopt::Long;
  1         10828  
  1         7  
7 1     1   958 use YAML::XS;
  1         3558  
  1         57  
8 1     1   988 use Capture::Tiny 'capture';
  1         28297  
  1         890  
9              
10             has config => (is => 'ro', default => sub{[]});
11              
12             has file => (is => 'ro', default => 'AYCABTU');
13             has action => (is => 'ro', default => 'list');
14             has show => (is => 'ro', default => '');
15             has tags => (is => 'ro', default => sub{[]});
16             has names => (is => 'ro', default => sub{[]});
17             has all => (is => 'ro', default => 0);
18             has quiet => (is => 'ro', default => 0);
19             has verbose => (is => 'ro', default => 0);
20             has args => (is => 'ro', default => sub{[]});
21              
22             has repos => (is => 'ro', default => sub{[]});
23              
24             my ($prefix, $error, $quiet, $normal, $verbose);
25              
26             sub run {
27 0     0 0   my $self = shift;
28 0 0 0       my @opts = @_
29             ? @_
30             : split /\s+/, ($ENV{AYCABTU_DEFAULT_OPTS} || '');
31 0           $self->get_options(@opts);
32 0           $self->read_config();
33 0           $self->select_repos();
34 0 0 0       if (not @{$self->repos} and not @{$self->names}) {
  0            
  0            
35 0           print "No repositories selected. Try --all.\n";
36 0           return;
37             }
38 0           my $action = $self->action;
39 0           my $method = "action_$action";
40 0 0         die "Can't perform action '$action'\n"
41             unless $self->can($method);
42 0           for my $entry (@{$self->repos}) {
  0            
43 0           ($prefix, $error, $quiet, $normal, $verbose) = ('') x 5;
44 0           $self->$method($entry);
45 0   0       $verbose ||= $normal;
46 0   0       $normal ||= $quiet;
47 0 0         my $msg =
    0          
    0          
48             $error ? $error :
49             $self->verbose ? $verbose :
50             $self->quiet ? $quiet :
51             $normal;
52 0 0         $msg = "$prefix$msg\n" if $msg;
53 0           print $msg;
54             }
55 0 0         if (@{$self->names}) {
  0            
56 0           warn "The following names were not found: @{$self->names}\n";
  0            
57             }
58             }
59              
60             sub get_options {
61 0     0 0   my $self = shift;
62 0           local @ARGV = @_;
63             GetOptions(
64 0     0     'file=s' => sub { $self->file($_[1]) },
65 0     0     'verbose' => sub { $self->verbose(1) },
66 0     0     'quiet' => sub { $self->quiet(1) },
67 0     0     'list' => sub { $self->action('list') },
68 0     0     'update' => sub { $self->action('update') },
69 0     0     'status' => sub { $self->action('status') },
70 0     0     'show=s' => sub { $self->action('show'); $self->show($_[1]) },
  0            
71 0     0     'all' => sub { $self->all(1) },
72             'tags=s' => sub {
73 0 0   0     my $tags = $_[1] or return;
74 0           push @{$self->tags}, [split ',', $tags];
  0            
75             },
76 0           'help' => \&help,
77             );
78 1     1   8 no warnings;
  1         1  
  1         2296  
79 0           my $names;
80 0           if (1 or not -t stdin) {
81 0           $names = [
82             map {
83 0           s!/$!!;
84 0 0         /^(\d+)-(\d+)?$/ ? ($1..$2) :
    0          
    0          
85             /^(\d+)$/ ? ($1) :
86             (-d) ? ($_) :
87             ();
88             } @ARGV
89             ];
90             }
91             else {
92             $names = [ split /\s+/, do {local $/; } ]
93             }
94 0           $self->names($names);
95 0           die "Can't locate aycabtu config file '${\ $self->file}'. Use --file=... option\n"
96 0 0 0       if not -e $self->file and not @{[glob $self->file . '*']};
97             }
98              
99             sub read_yaml {
100 0     0 0   my $self = shift;
101 0           my @files = glob($self->file . '*');
102 0           my $yaml = '';
103 0           local $/;
104 0           for my $file (@files) {
105 0           open Y, $file;
106 0           $yaml .= ;
107             }
108 0           return $yaml;
109             }
110              
111             sub read_config {
112 0     0 0   my $self = shift;
113 0           my $yaml = $self->read_yaml();
114 0           my $config = YAML::XS::Load($yaml);
115 0           $self->config($config);
116 0           die $self->file . " must be a YAML sequence of mapping"
117             if (ref($config) ne 'ARRAY') or grep {
118 0 0 0       ref ne 'HASH'
119             } @$config;
120 0           my $count = 1;
121 0           for my $entry (@$config) {
122 0 0         my $repo = $entry->{repo}
123             or die "No 'repo' field for entry $count";
124              
125 0           $entry->{_num} = $count++;
126              
127 0   0       $entry->{name} ||= '';
128 0 0 0       if (not $entry->{name} and $repo =~ /.*\/(.*).git$/) {
129 0           my $name = $1;
130             # XXX This should be configable.
131 0           $name =~ s/\.wiki$/-wiki/;
132 0           $entry->{name} = $name;
133             }
134              
135 0   0       my $type = $entry->{type} || '';
136 0 0 0       $type ||=
    0          
137             ($repo =~ /\.git$/) ? 'git' :
138             ($repo =~ /svn/) ? 'svn' :
139             '';
140 0           $entry->{type} = $type;
141              
142 0   0       my $tags = $entry->{tags} || '';
143              
144 0 0         my $set = $tags ? { map {($_, 1)} split /[\s\,]+/, $tags } : {};
  0            
145 0           my $str = $repo;
146 0           $str =~ s/\/$//;
147 0           $str =~ s/\/trunk$//;
148 0           $str =~ s/.*\///;
149 0           my $subst = {
150             py => 'python',
151             pm => 'perl',
152             };
153 0 0         $set->{$_} = 1 for map {$subst->{$_} || $_} split /[^\w]+/, $str;
  0            
154 0           $set->{$type} = 1;
155 0           delete $set->{''};
156              
157 0           $entry->{tags} = [ sort map lc, keys %$set ];
158             }
159             }
160              
161             sub select_repos {
162 0     0 0   my $self = shift;
163              
164 0           my $config = $self->config;
165 0           my $repos = $self->repos;
166 0           my $names = $self->names;
167              
168 0           my $last = 0;
169             OUTER:
170 0           for my $entry (@$config) {
171 0 0         last if $last;
172 0 0         next if $entry->{skip};
173 0 0         $last = 1 if $entry->{last};
174              
175 0 0         if ($self->all) {
176 0           push @$repos, $entry;
177 0           next;
178             }
179 0           my ($num, $name) = @{$entry}{qw(_num name)};
  0            
180 0 0         if (@$names) {
181 0 0         if (grep {$_ eq $name or $_ eq $num} @$names) {
  0 0          
182 0           push @$repos, $entry;
183 0           @$names = grep {$_ !~ /^(\Q$name\E|$num)$/} @$names;
  0            
184 0           next;
185             }
186             }
187 0           for my $tags (@{$self->tags}) {
  0            
188 0 0         if ($tags) {
189 0           my $count = scalar grep {
190 0           my $t = $_;
191 0           grep {$_ eq $t} @{$entry->{tags}};
  0            
  0            
192             } @$tags;
193 0 0         if ($count == @$tags) {
194 0           push @$repos, $entry;
195 0           next OUTER;
196             }
197             }
198             }
199             }
200             }
201              
202             sub action_update {
203 0     0 0   my $self = shift;
204 0           my $entry = shift;
205 0 0         $self->_check(update => $entry) or return;
206 0           my ($num, $name) = @{$entry}{qw(_num name)};
  0            
207 0           $prefix = "$num) Updating $name... ";
208 0           $self->git_update($entry);
209             }
210              
211             sub action_status {
212 0     0 0   my $self = shift;
213 0           my $entry = shift;
214 0 0         $self->_check('check status' => $entry) or return;
215 0           my ($num, $name) = @{$entry}{qw(_num name)};
  0            
216 0           $prefix = "$num) Status for $name... ";
217 0           $self->git_status($entry);
218             }
219              
220             sub action_list {
221 0     0 0   my $self = shift;
222 0           my $entry = shift;
223 0           my ($num, $repo, $name, $type, $tags) = @{$entry}{qw(_num repo name type tags)};
  0            
224 0           $prefix = "$num) ";
225 0           $quiet = $name;
226 0           $normal = sprintf " %-25s %-4s %-50s", $name, $type, $repo;
227 0           $verbose = "$normal\n tags: @$tags";
228             }
229              
230             sub action_show {
231 0     0 0   my $self = shift;
232 0           my $entry = shift;
233 0           my $show = $self->show;
234 0           $prefix = '';
235 0 0         if ($show =~ /^(nums?|numbers?)$/) {
    0          
    0          
236 0           $quiet = $entry->{_num};
237             }
238             elsif ($show =~ /^names?$/) {
239 0           $quiet = $entry->{name};
240             }
241             elsif ($show =~ /^tags?$/) {
242 0           my $set = {};
243 0           for my $repo (@{$self->repos}) {
  0            
244 0           $set->{$_} = 1 for @{$repo->{tags}};
  0            
245             }
246 0           my @tags = sort keys %$set;
247 0           print "@tags\n";
248 0           exit;
249             }
250             else {
251 0           $error = "Invalid type '$show' to show.";
252             }
253             }
254              
255             sub _check {
256 0     0     my $self = shift;
257 0           my $action = shift;
258 0           my $entry = shift;
259 0           my ($num, $repo, $name, $type) = @{$entry}{qw(_num repo name type)};
  0            
260 0 0         if (not $name) {
261 0           $error = "Can't $action $repo. No name.";
262 0           return;
263             }
264 0 0         if (not $type) {
265 0           $error = "Can't $action $name. Unknown type.";
266 0           return;
267             }
268 0 0         if ($type ne 'git') {
269 0           $error = "Can't $action $name. Type $type not yet supported.";
270 0           return;
271             }
272 0           return 1;
273             }
274              
275             sub git_update {
276 0     0 0   my $self = shift;
277 0           my $entry = shift;
278 0           my ($repo, $name) = @{$entry}{qw(repo name)};
  0            
279 0 0         if (not -d $name) {
    0          
280 0           my $cmd = "git clone $repo $name";
281 0     0     my ($o, $e) = capture { system($cmd) };
  0            
282 0 0         if ($e =~ /\S/) {
283 0           $quiet = 'Error';
284 0           $verbose = "\n$o$e";
285             }
286             else {
287 0           $normal = 'Done';
288             }
289             }
290             elsif (-d "$name/.git") {
291 0     0     my ($o, $e) = capture { system("cd $name; git pull origin master") };
  0            
292 0 0         if ($o eq "Already up-to-date.\n") {
    0          
293 0           $normal = "Already up to date";
294             }
295             elsif ($e) {
296 0           $quiet = "Failed";
297 0           $verbose = "\n$o$e";
298             }
299             else {
300 0           $quiet = "Updated";
301 0           $verbose = "\n$o$e";
302             }
303             }
304             else {
305 0           $quiet = "Skipped";
306             }
307             }
308              
309             sub git_status {
310 0     0 0   my $self = shift;
311 0           my $entry = shift;
312 0           my ($repo, $name) = @{$entry}{qw(repo name)};
  0            
313 0 0         if (not -d $name) {
    0          
314 0           $error = "No local repository";
315             }
316             elsif (-d "$name/.git") {
317 0     0     my ($o, $e) = capture { system("cd $name; git status") };
  0            
318 0 0 0       if ($o =~ /^nothing to commit/m and
319             not $e
320             ) {
321 0 0         if ($o =~ /Your branch is ahead .* by (\d+) /) {
322 0           $quiet = "Ahead by $1";
323 0           $verbose = "\n$o$e";
324             }
325             else {
326 0           $normal = "OK";
327             }
328             }
329             else {
330 0           $quiet = "Dirty";
331 0           $verbose = "\n$o$e";
332             }
333             }
334             else {
335 0           $quiet= "Skipped";
336             }
337             }
338              
339             sub help {
340 0     0 0   print <<'...';
341             Usage:
342             aycabtu [ options ] action selectors
343              
344             Options:
345             --file=file # aycabtu config file. Default: 'AYCABTU'
346             --verbose # Show more information
347             --quiet # Show less information
348              
349             Action:
350             --list # List the selected repos (default action)
351             --update # Checkout or update the selected repos
352             --status # Get status info on the selected repos
353             --show=aspect # Show some aspect of the selected repos
354              
355             Show Aspects:
356             numbers # Show the numbers of the selected repos
357             names # Show the numbers of the selected repos
358             tags # Show ALL tags of selected repos
359              
360             Selector:
361             --all # Use all the repos in the config file
362             --tags=tags # Select repos matching all the tags
363             Can be used more than once
364             names # A list of the names to to select. You can use
365             # multiple names and file globbing, like this:
366              
367             aycabtu --update foo-repo bar-*-repo
368              
369             ...
370 0           exit;
371             }
372              
373             1;