File Coverage

blib/lib/App/count.pm
Criterion Covered Total %
statement 121 122 99.1
branch 57 64 89.0
condition 27 32 84.3
subroutine 24 24 100.0
pod 1 1 100.0
total 230 243 94.6


line stmt bran cond sub pod time code
1             package App::count;
2              
3 31     31   1168153 use strict;
  31         81  
  31         1169  
4 31     31   167 use warnings;
  31         44  
  31         1489  
5              
6             # ABSTRACT: Counting utility for a file consisting of the fixed number of fields like CSV
7             our $VERSION = 'v0.1.1'; # VERSION
8              
9 31     31   41601 use Getopt::Long qw(GetOptionsFromArray);
  31         599643  
  31         223  
10 31     31   41005 use Getopt::Config::FromPod;
  31         1374996  
  31         992  
11 31     31   56115 use Pod::Usage;
  31         868893  
  31         4740  
12 31     31   85054 use YAML::Any;
  31         26129  
  31         174  
13 31     31   309884 use Encode;
  31         93  
  31         3164  
14 31     31   24005 use String::Unescape;
  31         55646  
  31         81263  
15              
16             Getopt::Long::Configure('posix_default', 'no_ignore_case');
17              
18             my $yaml = YAML::Any->implementation;
19             my $encoder = $yaml eq 'YAML::Syck' || $yaml eq 'YAML::Old' ? sub { shift; } : sub { Encode::encode('utf-8', shift); };
20              
21             my $reorder = sub {
22             my ($spec, @F) = @_;
23             return @F unless length($spec);
24             my (@ret, %used);
25             foreach my $idx (0..$#F) {
26             my $index;
27             if($idx < @$spec && $spec->[$idx] ne '') {
28             $index = ($spec->[$idx] > 0) ? ($spec->[$idx] - 1) : ($spec->[$idx] + @F);
29             } else {
30             my $index_ = 0;
31             while(exists $used{$index_}) { ++$index_; }
32             $index = $index_;
33             }
34             push @ret, $F[$index];
35             $used{$index} = 1;
36             }
37             return @ret;
38             };
39              
40             sub run
41             {
42 59 50 33 59 1 84567534 shift if @_ && eval { $_[0]->isa(__PACKAGE__) };
  59         7048  
43 59         693 my @spec;
44 59         294 my $max_col = 0;
45 59 100   28   2027 my $handler = sub { my $key = $_[0]; push @spec, map { $max_col = $_ if $max_col < $_; [$key, $_-1 ] } split /,/, $_[1]; };
  28         587921  
  28         126  
  29         1135  
  29         267  
46             my %opts = (
47 8     8   115852 c => sub { push @spec, ['count']; },
48             sum => $handler, max => $handler, min => $handler, avg => $handler,
49             'map' => sub {
50 17     17   537815 my @t = split /,/, $_[1];
51 17         158 while(my ($idx, $key) = splice(@t, 0, 2)) {
52 20 100       439 $max_col = $idx if $max_col < $idx;
53 20         1599 push @spec, ['map', $idx-1, $key];
54             }
55             }
56 59         2727 );
57 59 50       1927 GetOptionsFromArray(\@_, \%opts, Getopt::Config::FromPod->array) or pod2usage(-verbose => 0);
58 59 50       775653 pod2usage(-verbose => 0) if exists $opts{h};
59 59 50       463 pod2usage(-verbose => 2) if exists $opts{help};
60 59 100       223 die "Column number MUST be more than 0" if grep { $_->[1] < 0 } @spec;
  57         1475  
61              
62 43         171 my $map;
63 43 100 50     587 $map = YAML::Any::LoadFile($opts{M}) or die "Can't load map file" if exists $opts{M};
64 41 100 100     379018 die "map is specified but map file is not specified" if ! defined $map && grep { $_->[0] eq 'map' } @spec;
  23         770  
65 39 100 100     430 die "Map key is not found in map file" if defined $map && grep { ! exists $map->{$_} } map { $_->[2] } grep { $_->[0] eq 'map' } @spec;
  14         180  
  14         46  
  18         83  
66 37         86 my $group;
67 37 100       232 if(exists $opts{g}) {
68 22 100 100     73 if(@{$opts{g}} == 1 && $opts{g}[0] eq '*') {
  22         1602  
69 1         3 $group = [];
70             } else {
71 21 100       86 $group = [map { $max_col = $_ if $max_col < $_; $_-1 } map { split /,/ } @{$opts{g}}];
  30         822  
  30         171  
  24         102  
  21         286  
72             }
73             }
74 37 100 100     484 die "Column number MUST be more than 0" if defined $group && grep { $_ < 0 } @$group;
  30         1388  
75 33 100       406 push @spec, ['count'] if ! @spec;
76 33 100       274 if(exists $opts{r}) {
77 10         60 $opts{r} = [ split /,/, $opts{r} ];
78 10         101 foreach my $idx (@{$opts{r}}) {
  10         696  
79 18 100       642 $max_col = $idx if $max_col < $idx;
80             }
81             }
82 33 100 100     353 die 'Column number MUST NOT be 0' if exists $opts{r} && grep { length != 0 && $_ == 0 } @{$opts{r}};
  18 100       299  
  10         38  
83 29 100       1079 my $odelimiter = defined($opts{t}) ? String::Unescape->unescape($opts{t}) : "\t";
84 29 100       543 $opts{t} = defined($opts{t}) ? String::Unescape->unescape($opts{t}) : '\s+';
85              
86             my %init = (
87 7     7   43 max => sub { undef },
88 7     7   45 min => sub { undef },
89 4     4   32 avg => sub { [0,0] }, # Return new array reference
90 35     35   216 sum => sub { 0 },
91 56     56   176 count => sub { 0 },
92 54     54   244 'map' => sub { undef },
93 29         3282 );
94              
95 29 100       171 push @_, '-' if ! @_;
96 29         148 while(my $file = shift @_) {
97 29         87 my $fh;
98 29 100       177 if($file ne '-') {
99 1 50       84 open $fh, '<', $file or die "Can't open $file";
100             } else {
101 28         84 $fh = \*STDIN;
102             }
103              
104 28         231 my %data;
105             my %proc = ( # $key, $idx, \@F
106 8 100 66 8   39 max => sub { my ($key, $idx, $F) = @_; $data{$key}[$idx] = $F->[$spec[$idx][1]] if ! defined $data{$key}[$idx] || $data{$key}[$idx] < $F->[$spec[$idx][1]]; },
  8         60  
107 8 50 66 8   38 min => sub { my ($key, $idx, $F) = @_; $data{$key}[$idx] = $F->[$spec[$idx][1]] if ! defined $data{$key}[$idx] || $data{$key}[$idx] > $F->[$spec[$idx][1]]; },
  8         167  
108 8     8   42 avg => sub { my ($key, $idx, $F) = @_; ++$data{$key}[$idx][0]; $data{$key}[$idx][1] += $F->[$spec[$idx][1]]; },
  8         13  
  8         188  
109 44     44   195 sum => sub { my ($key, $idx, $F) = @_; $data{$key}[$idx] += $F->[$spec[$idx][1]]; },
  44         506  
110 103     103   175 count => sub { my ($key, $idx, $F) = @_; ++$data{$key}[$idx]; },
  103         632  
111 78   100 78   132 'map' => sub { my ($key, $idx, $F) = @_; $data{$key}[$idx] ||= $encoder->($map->{$spec[$idx][2]}{$F->[$spec[$idx][1]]}); },
  78         568  
112 28         11366 );
113 28         1188 while(<$fh>) {
114 142         2153 s/[\r\n]+$//;
115 142         1105 my @F = split /$opts{t}/;
116 142 100       407 if(@F < $max_col) {
117 11         756 warn 'Wrong delimiter?: '.scalar(@F).' field(s) is/are fewer than '.$max_col.' specified in the option';
118 11         43 $max_col = 0; # Avoid repeated warnings
119             }
120 142 100       844 my $key = defined $group ? join("\x00", @$group == 0 ? @F : @F[@$group]) : '_';
    100          
121              
122 142         587 foreach my $idx (0..$#spec) {
123 249   100     7045 $data{$key}[$idx] ||= $init{$spec[$idx][0]}->();
124 249         772 $proc{$spec[$idx][0]}->($key, $idx, \@F);
125             }
126             }
127              
128 28 50       259 if($file ne '-') {
129 0         0 close $fh;
130             }
131              
132 28         362 foreach my $key (sort keys %data) {
133 73         130 my @F;
134 73 100       339 push @F, split /\x00/, $key if exists $opts{g};
135 73 100       129 push @F, map { ref $_ ? $_->[1]/$_->[0] : $_ } @{$data{$key}};
  145         528  
  73         213  
136 73         727 print join($odelimiter, $reorder->($opts{r}, @F)), "\n";
137             }
138             }
139             }
140              
141             1;
142              
143             __END__