File Coverage

blib/lib/App/Sets.pm
Criterion Covered Total %
statement 99 116 85.3
branch 35 72 48.6
condition 2 11 18.1
subroutine 18 18 100.0
pod 0 5 0.0
total 154 222 69.3


line stmt bran cond sub pod time code
1             package App::Sets;
2             $App::Sets::VERSION = '0.978';
3              
4              
5 3     3   252739 use strict;
  3         27  
  3         93  
6 3     3   16 use warnings;
  3         6  
  3         92  
7              
8             # ABSTRACT: set operations in Perl
9              
10 3     3   637 use English qw( -no_match_vars );
  3         3723  
  3         17  
11 3     3   1116 use 5.010;
  3         11  
12             use Getopt::Long
13 3     3   2278 qw< GetOptionsFromArray :config pass_through no_ignore_case bundling >;
  3         38764  
  3         20  
14 3     3   2326 use Pod::Usage qw< pod2usage >;
  3         150036  
  3         308  
15 3     3   1928 use Log::Log4perl::Tiny qw< :easy :dead_if_first LOGLEVEL >;
  3         46797  
  3         22  
16 3     3   2604 use App::Sets::Parser;
  3         9  
  3         102  
17 3     3   1301 use App::Sets::Iterator;
  3         8  
  3         90  
18 3     3   1339 use App::Sets::Operations;
  3         7  
  3         101  
19 3     3   1279 use App::Sets::Sort qw< sort_filehandle >;
  3         9  
  3         3707  
20              
21             my %config = (
22             binmode => ':raw:encoding(UTF-8)',
23             loglevel => 'INFO',
24             parsedebug => 0,
25             );
26              
27             sub populate_config {
28 64     64 0 535 my (@args) = @_;
29              
30 64 50       377 $config{sorted} = 1 if $ENV{SETS_SORTED};
31 64 50       244 $config{trim} = 1 if $ENV{SETS_TRIM};
32 64 50       174 $config{cache} = $ENV{SETS_CACHE} if exists $ENV{SETS_CACHE};
33             $config{loglevel} = $ENV{SETS_LOGLEVEL}
34 64 50       334 if exists $ENV{SETS_LOGLEVEL};
35             $config{parsedebug} = $ENV{SETS_PARSEDEBUG}
36 64 50       212 if exists $ENV{SETS_PARSEDEBUG};
37             $config{internal_sort} = $ENV{SETS_INTERNAL_SORT}
38 64 100       481 if exists $ENV{SETS_INTERNAL_SORT};
39 64 50       321 $config{binmode} = $ENV{SETS_BINMODE} if $ENV{SETS_BINMODE};
40 64 50       815 GetOptionsFromArray(
41             \@args, \%config, qw< man help usage version
42             binmode|b=s
43             cache|cache-sorted|S=s
44             internal_sort|internal-sort|I!
45             loglevel|l=s
46             sorted|s!
47             trim|t!
48             >
49             )
50             or pod2usage(
51             -verbose => 99,
52             -sections => 'USAGE',
53             );
54 64 50 0     67484 $App::Sets::VERSION
55             //= '0.972' unless defined $App::Sets::VERSION;
56             pod2usage(message => "$0 $App::Sets::VERSION", -verbose => 99,
57             -sections => ' ')
58 64 50       211 if $config{version};
59             pod2usage(
60             -verbose => 99,
61             -sections => 'USAGE'
62 64 50       154 ) if $config{usage};
63             pod2usage(
64             -verbose => 99,
65             -sections => 'USAGE|EXAMPLES|OPTIONS'
66 64 50       149 ) if $config{help};
67 64 50       165 pod2usage(-verbose => 2) if $config{man};
68              
69 64         670 LOGLEVEL $config{loglevel};
70              
71             $config{cache} = '.sorted'
72             if exists $config{cache}
73 64 50 0     2425 && !(defined($config{cache}) && length($config{cache}));
      33        
74 64 50       149 $config{sorted} = 1 if exists $config{cache};
75              
76 64 50       227 if (exists $config{cache}) {
    50          
77 0         0 INFO "using sort cache or generating it when not available";
78             }
79             elsif ($config{sorted}) {
80 0         0 INFO "assuming input files are sorted";
81             }
82             INFO "trimming away leading/trailing whitespaces"
83 64 50       167 if $config{trim};
84              
85 64 50       154 pod2usage(
86             -verbose => 99,
87             -sections => 'USAGE',
88             ) unless @args;
89              
90 64         223 return @args;
91             } ## end sub populate_config
92              
93             sub run {
94 64     64 0 70909 my $package = shift;
95 64         223 my @args = populate_config(@_);
96              
97 64         130 my $input;
98 64 50       158 if (@args > 1) {
99 64 50       197 shift @args if $args[0] eq '--';
100 64 50       180 LOGDIE "only file op file [op file...] "
101             . "with multiple parameters (@args)...\n"
102             unless @args % 2;
103 64         95 my @chunks;
104 64         155 while (@args) {
105 128         401 push @chunks, escape(shift @args);
106 128 100       415 push @chunks, shift @args if @args;
107             }
108 64         269 $input = join ' ', @chunks;
109             } ## end if (@args > 1)
110             else {
111 0         0 $input = shift @args;
112             }
113              
114 64 50       203 LOGLEVEL('DEBUG') if $config{parsedebug};
115 64         650 DEBUG "parsing >$input<";
116 64         2571 my $expression = App::Sets::Parser::parse($input, 0);
117 64         275 LOGLEVEL($config{loglevel});
118              
119 64     2   2633 binmode STDOUT, $config{binmode};
  2         16  
  2         4  
  2         11  
120              
121 64         7978 my $it = expression($expression);
122 64         409 while (defined(my $item = $it->drop())) {
123 548         742 print {*STDOUT} $item;
  548         4581  
124 548 50       1973 print {*STDOUT} "\n" if $config{trim};
  0         0  
125             }
126 64         3361 return;
127             } ## end sub run
128              
129             sub escape {
130 128     128 0 228 my ($text) = @_;
131 128         1183 $text =~ s{(\W)}{\\$1}gmxs;
132 128         364 return $text;
133             }
134              
135             sub expression {
136 192     192 0 575 my ($expression) = @_;
137 192 100       447 if (ref $expression) { # operation
138 64         155 my ($op, $l, $r) = @$expression;
139 64         628 my $sub = App::Sets::Operations->can($op);
140 64         182 return $sub->(expression($l), expression($r));
141             }
142             else { # plain file
143 128         432 return file($expression);
144             }
145             } ## end sub expression
146              
147             sub file {
148 128     128 0 239 my ($filename) = @_;
149 128 50 33     5309 LOGDIE "invalid file '$filename'\n"
150             unless -r $filename && !-d $filename;
151              
152 128 50       524 if ($config{cache}) {
153 0         0 my $cache_filename = $filename . $config{cache};
154 0 0       0 if (!-e $cache_filename) { # generate cache file
155 0         0 WARN "generating cached sorted file "
156             . "'$cache_filename', might wait a bit...";
157 0         0 my $ifh = sort_filehandle($filename, \%config);
158 0 0       0 open my $ofh, '>', $cache_filename
159             or LOGDIE "open('$cache_filename') for output: $OS_ERROR";
160 0         0 while (<$ifh>) {
161 0         0 print {$ofh} $_;
  0         0  
162             }
163 0 0       0 close $ofh or LOGDIE "close('$cache_filename'): $OS_ERROR";
164             } ## end if (!-e $cache_filename)
165 0         0 INFO "using '$cache_filename' (assumed to be sorted) "
166             . "instead of '$filename'";
167 0         0 $filename = $cache_filename;
168             } ## end if ($config{cache})
169              
170 128         191 my $fh;
171 128 50       292 if ($config{sorted}) {
172             INFO "opening '$filename', assuming it is already sorted"
173 0 0       0 unless $config{cache};
174 0 0       0 open $fh, '<', $filename
175             or LOGDIE "open('$filename'): $OS_ERROR";
176             } ## end if ($config{sorted})
177             else {
178 128         1475 INFO "opening '$filename' and sorting on the fly";
179 128         4947 $fh = sort_filehandle($filename, \%config);
180             }
181             return App::Sets::Iterator->new(
182             sub {
183 1520     1520   15885 my $retval = <$fh>;
184 1520 100       3874 return unless defined $retval;
185             $retval =~ s{\A\s+|\s+\z}{}gmxs
186 1216 50       2100 if $config{trim};
187 1216         3764 return $retval;
188             }
189 128         6596 );
190             } ## end sub file
191              
192             1;
193              
194             __END__