File Coverage

blib/lib/App/PerlGlue.pm
Criterion Covered Total %
statement 96 160 60.0
branch 27 74 36.4
condition 22 57 38.6
subroutine 14 19 73.6
pod 0 1 0.0
total 159 311 51.1


line stmt bran cond sub pod time code
1             package App::PerlGlue;
2              
3 8     8   212965 use strict;
  8         14  
  8         289  
4 8     8   34 use warnings;
  8         18  
  8         419  
5 8     8   38 use feature qw(say);
  8         10  
  8         1112  
6 8     8   5389 use JSON::PP qw(encode_json decode_json);
  8         135162  
  8         752  
7 8     8   4227 use Text::ParseWords qw(parse_line);
  8         12666  
  8         17680  
8              
9             our $VERSION = '0.04';
10              
11             sub run {
12 8     8 0 496 my ($class, @argv) = @_;
13 8   50     53 my $cmd = shift @argv // 'help';
14              
15 8 50 33     68 return _help() if $cmd eq 'help' || $cmd eq '--help' || $cmd eq '-h';
      33        
16              
17 8 50 66     177 if ($cmd eq 'version' || $cmd eq '--version' || $cmd eq '-v') {
      66        
18 2         21 say "perlglue $VERSION";
19 2         3 return 0;
20             }
21              
22 6 100 66     48 return _cmd_command_help($cmd) if @argv && ($argv[0] eq '--help' || $argv[0] eq '-h');
      66        
23              
24 5 100       15 return _cmd_upper() if $cmd eq 'upper';
25 4 100       16 return _cmd_lower() if $cmd eq 'lower';
26 3 100       10 return _cmd_lines(@argv) if $cmd eq 'lines';
27 2 50       7 return _cmd_lines(@argv) if $cmd eq 'where';
28 2 50       11 return _cmd_replace(@argv) if $cmd eq 'replace';
29 2 100       11 return _cmd_pick(@argv) if $cmd eq 'pick';
30 1 0 33     5 return _cmd_convert(@argv) if $cmd eq 'convert' || $cmd eq 'csv' || $cmd eq 'from-csv';
      33        
31 0 0       0 return _cmd_jsonl(@argv) if $cmd eq 'jsonl';
32 0 0       0 return _cmd_template(@argv) if $cmd eq 'template';
33 0 0       0 return _cmd_rename(@argv) if $cmd eq 'rename';
34              
35 0         0 warn "Unknown command: $cmd\n\n";
36 0         0 _help();
37 0         0 return 2;
38             }
39              
40 1     1   47659 sub _cmd_upper { while (my $line = ) { print uc $line } return 0 }
  1         3777  
  1         0  
41 1     1   111571 sub _cmd_lower { while (my $line = ) { print lc $line } return 0 }
  1         5858  
  1         0  
42              
43             sub _open_in {
44 3     3   11 my ($file) = @_;
45 3 100 66     20 return *STDIN unless defined $file && length $file;
46 2 50       108 open my $fh, '<', $file or die "Cannot open $file: $!";
47 2         6 return $fh;
48             }
49              
50             sub _cmd_lines {
51 1     1   2 my (@argv) = @_;
52 1         2 my ($file, $expr);
53 1         3 while (@argv) {
54 1         1 my $arg = shift @argv;
55 1 50       4 if ($arg eq '--where') { $expr = shift @argv; }
  1 0       3  
    0          
56 0         0 elsif (!defined $file) { $file = $arg; }
57 0         0 elsif (!defined $expr) { $expr = $arg; }
58             }
59 1         4 my $fh = _open_in($file);
60 1         22710 while (my $line = <$fh>) {
61 2         7 local $_ = $line;
62 2 50       7 if (defined $expr) {
63 2         253 my $ok = eval $expr;
64 2 100       15 next unless $ok;
65             }
66 1         3765 print $line;
67             }
68 1         0 return 0;
69             }
70              
71             sub _cmd_replace {
72 0     0   0 my (@argv) = @_;
73 0   0     0 my $expr = shift @argv // die "replace requires perl substitution expression\n";
74 0         0 my $fh = _open_in(shift @argv);
75 0         0 while (my $line = <$fh>) {
76 0         0 local $_ = $line;
77 0         0 eval $expr;
78 0         0 print $_;
79             }
80 0         0 return 0;
81             }
82              
83             sub _parse_csv_rows {
84 2     2   21 my ($fh) = @_;
85 2         2 my @rows;
86 2         59 while (my $line = <$fh>) {
87 6         573 chomp $line;
88 6 50       10 next if $line eq '';
89 6         20 push @rows, [ parse_line(',', 1, $line) ];
90             }
91 2         106 return @rows;
92             }
93              
94             sub _cmd_pick {
95 1     1   6 my (@argv) = @_;
96 1   50     5 my $file = shift @argv // die "pick requires file\n";
97 1   50     5 my $flag = shift @argv // '';
98 1 50       5 die "pick expects --csv name,email\n" unless $flag eq '--csv';
99 1   50     7 my @wanted = split /,/, (shift(@argv) // '');
100              
101 1         10 my $fh = _open_in($file);
102 1         4 my @rows = _parse_csv_rows($fh);
103 1   50     5 my $header = shift @rows // [];
104 1         1 my %idx; @idx{@$header} = (0 .. $#$header);
  1         7  
105              
106 1         11 say join ',', @wanted;
107 1         2 for my $r (@rows) {
108 2 50       3 my @out = map { defined $idx{$_} ? $r->[ $idx{$_} ] : '' } @wanted;
  4         16  
109 2         5 say join ',', @out;
110             }
111 1         0 return 0;
112             }
113              
114             sub _cmd_convert {
115 1     1   3 my (@argv) = @_;
116 1   50     3 my $file = shift @argv // die "convert requires file\n";
117 1         1 my $to;
118 1         15 while (@argv) {
119 1         2 my $a = shift @argv;
120 1 50       4 $to = shift @argv if $a eq '--to';
121             }
122 1 50 33     6 die "convert only supports --to jsonl\n" unless defined $to && $to eq 'jsonl';
123              
124 1         4 my $fh = _open_in($file);
125 1         4 my @rows = _parse_csv_rows($fh);
126 1   50     4 my $header = shift @rows // [];
127 1         2 for my $r (@rows) {
128 2         233 my %obj;
129 2         7 @obj{@$header} = @$r;
130 2         8 say encode_json(\%obj);
131             }
132 1         0 return 0;
133             }
134              
135             sub _cmd_jsonl {
136 0     0   0 my (@argv) = @_;
137 0         0 my ($file, $expr, $where);
138 0         0 while (@argv) {
139 0         0 my $a = shift @argv;
140 0 0 0     0 if ($a eq '--where') { $where = shift @argv; }
  0 0       0  
    0          
141 0         0 elsif (!defined $file && $a !~ /^\$_\-/) { $file = $a; }
142 0         0 elsif (!defined $expr) { $expr = $a; }
143             }
144 0 0       0 $expr = $where if defined $where;
145 0         0 my $fh = _open_in($file);
146 0         0 while (my $line = <$fh>) {
147 0         0 chomp $line;
148 0 0       0 next if $line eq '';
149 0         0 local $_ = decode_json($line);
150 0 0       0 if (defined $expr) {
151 0         0 my $ok = eval $expr;
152 0 0       0 next unless $ok;
153             }
154 0         0 say encode_json($_);
155             }
156 0         0 return 0;
157             }
158              
159             sub _cmd_template {
160 0     0   0 my (@argv) = @_;
161 0   0     0 my $file = shift @argv // die "template requires file\n";
162 0   0     0 my $tpl = shift @argv // die "template requires template string\n";
163              
164 0         0 my $fh = _open_in($file);
165 0         0 my @rows = _parse_csv_rows($fh);
166 0   0     0 my $header = shift @rows // [];
167 0         0 for my $r (@rows) {
168 0         0 my %obj;
169 0         0 @obj{@$header} = @$r;
170 0 0       0 (my $out = $tpl) =~ s/\{\{\s*(\w+)\s*\}\}/defined $obj{$1} ? $obj{$1} : ''/ge;
  0         0  
171 0         0 say $out;
172             }
173 0         0 return 0;
174             }
175              
176             sub _cmd_rename {
177 0     0   0 my (@argv) = @_;
178 0   0     0 my $expr = shift @argv // die "rename requires substitution expression\n";
179 0         0 for my $old (@argv) {
180 0         0 (my $new = $old);
181 0         0 local $_ = $new;
182 0         0 eval $expr;
183 0         0 $new = $_;
184 0 0       0 next if $new eq $old;
185 0 0       0 die "Target exists: $new\n" if -e $new;
186 0 0       0 rename $old, $new or die "rename $old -> $new failed: $!";
187 0         0 say "$old -> $new";
188             }
189 0         0 return 0;
190             }
191              
192             sub _cmd_command_help {
193 1     1   5 my ($cmd) = @_;
194 1         17 my %usage = (
195             upper => 'perlglue upper < input.txt',
196             lower => 'perlglue lower < input.txt',
197             lines => 'perlglue lines [file] [--where EXPR]',
198             where => 'perlglue where [file] [--where EXPR]',
199             replace => q{perlglue replace 's/foo/bar/g' [file]},
200             pick => 'perlglue pick users.csv --csv name,email',
201             convert => 'perlglue convert users.csv --to jsonl',
202             csv => 'perlglue csv users.csv --to jsonl',
203             'from-csv' => 'perlglue from-csv users.csv --to jsonl',
204             jsonl => q{perlglue jsonl logs.jsonl '\$_->{status} >= 500'},
205             template => q{perlglue template users.csv 'Hello, {{name}}'},
206             rename => q{perlglue rename 's/\s+/_/g' files...},
207             version => 'perlglue version',
208             help => 'perlglue help',
209             );
210              
211 1 50       7 if (exists $usage{$cmd}) {
212 1         16 say $usage{$cmd};
213 1           return 0;
214             }
215              
216 0           warn "Unknown command: $cmd\n";
217 0           return 2;
218             }
219              
220             sub _help {
221 0     0     print <<'HELP';
222             perlglue - glue messy text into useful shapes
223              
224             Usage:
225             perlglue help
226             perlglue --help
227             perlglue version
228             perlglue --help
229             perlglue upper < input.txt
230             perlglue lower < input.txt
231             perlglue lines [file] [--where EXPR]
232             perlglue replace 's/foo/bar/g' [file]
233             perlglue pick users.csv --csv name,email
234             perlglue convert users.csv --to jsonl
235             perlglue jsonl logs.jsonl '$_->{status} >= 500'
236             perlglue template users.csv 'Hello, {{name}}'
237             perlglue rename 's/\s+/_/g' files...
238             HELP
239 0           return 0;
240             }
241              
242             1;
243              
244             __END__