File Coverage

blib/lib/Fsdb/Filter/dbcol.pm
Criterion Covered Total %
statement 15 85 17.6
branch 0 22 0.0
condition n/a
subroutine 5 15 33.3
pod 6 6 100.0
total 26 128 20.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             #
4             # dbcol.pm
5             # Copyright (C) 1991-2015 by John Heidemann
6             # $Id: 2607991a5498804e1c6e26d652175fe92db98515 $
7             #
8             # This program is distributed under terms of the GNU general
9             # public license, version 2. See the file COPYING
10             # in $dblibdir for details.
11             #
12              
13             package Fsdb::Filter::dbcol;
14              
15             =head1 NAME
16              
17             dbcol - select columns from an Fsdb file
18              
19             =head1 SYNOPSIS
20              
21             dbcol [-v] [-e -] [column...]
22              
23             =head1 DESCRIPTION
24              
25             Select one or more columns from the input database.
26             If a value is given for empty columns with the -e option,
27             then any named columns which don't exist will be created.
28             Otherwise, non-existent columns are an error.
29              
30             Note: a safer way to create columns is dbcolcreate.
31              
32             =head1 OPTIONS
33              
34             =over 4
35              
36             =item B<-r> or B<--relaxed-errors>
37              
38             Relaxed error checking: ignore columns that aren't there.
39              
40             =item B<-v> or B<--invert-match>
41              
42             Output all columns except those listed (like grep -v).
43              
44             =item B<-e> EmptyValue or B<--empty>
45              
46             Specify the value newly created columns get.
47              
48             =back
49              
50             =for comment
51             begin_standard_fsdb_options
52              
53             and the standard fsdb options:
54              
55             =over 4
56              
57             =item B<-d>
58              
59             Enable debugging output.
60              
61             =item B<-i> or B<--input> InputSource
62              
63             Read from InputSource, typically a file, or - for standard input,
64             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
65              
66             =item B<-o> or B<--output> OutputDestination
67              
68             Write to OutputDestination, typically a file, or - for standard output,
69             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
70              
71             =item B<--autorun> or B<--noautorun>
72              
73             By default, programs process automatically,
74             but Fsdb::Filter objects in Perl do not run until you invoke
75             the run() method.
76             The C<--(no)autorun> option controls that behavior within Perl.
77              
78             =item B<--help>
79              
80             Show help.
81              
82             =item B<--man>
83              
84             Show full manual.
85              
86             =back
87              
88             =for comment
89             end_standard_fsdb_options
90              
91              
92             =head1 SAMPLE USAGE
93              
94             =head2 Input:
95              
96             #fsdb account passwd uid gid fullname homedir shell
97             johnh * 2274 134 John_Heidemann /home/johnh /bin/bash
98             greg * 2275 134 Greg_Johnson /home/greg /bin/bash
99             root * 0 0 Root /root /bin/bash
100             # this is a simple database
101              
102             =head2 Command:
103              
104             cat DATA/passwd.fsdb account | dbcol account
105              
106             =head2 Output:
107              
108             #fsdb account
109             johnh
110             greg
111             root
112             # this is a simple database
113             # | dbcol account
114              
115              
116             =head1 SEE ALSO
117              
118             L,
119             L
120              
121             =head1 CLASS FUNCTIONS
122              
123             =cut
124              
125             @ISA = qw(Fsdb::Filter);
126             ($VERSION) = 2.0;
127              
128 1     1   5577 use strict;
  1         2  
  1         37  
129 1     1   5 use Pod::Usage;
  1         2  
  1         134  
130              
131 1     1   5 use Fsdb::Filter;
  1         2  
  1         19  
132 1     1   12 use Fsdb::IO::Reader;
  1         1  
  1         23  
133 1     1   5 use Fsdb::IO::Writer;
  1         1  
  1         905  
134              
135              
136             =head2 new
137              
138             $filter = new Fsdb::Filter::dbcol(@arguments);
139              
140             Create a new dbcol object, taking command-line arguments.
141              
142             =cut
143              
144             sub new {
145 0     0 1   my $class = shift @_;
146 0           my $self = $class->SUPER::new(@_);
147 0           bless $self, $class;
148 0           $self->set_defaults;
149 0           $self->parse_options(@_);
150 0           $self->SUPER::post_new();
151 0           return $self;
152             }
153              
154              
155             =head2 set_defaults
156              
157             $filter->set_defaults();
158              
159             Internal: set up defaults.
160              
161             =cut
162              
163             sub set_defaults ($) {
164 0     0 1   my($self) = @_;
165 0           $self->SUPER::set_defaults();
166 0           $self->{_null_value} = undef;
167 0           $self->{_invert_match} = undef;
168 0           $self->{_relaxed_errors} = undef;
169             }
170              
171             =head2 parse_options
172              
173             $filter->parse_options(@ARGV);
174              
175             Internal: parse options
176              
177             =cut
178              
179             sub parse_options ($@) {
180 0     0 1   my $self = shift @_;
181              
182 0           my(@arg_cols) = @_;
183             $self->get_options(
184             \@arg_cols,
185 0     0     'help|?' => sub { pod2usage(1); },
186 0     0     'man' => sub { pod2usage(-verbose => 2); },
187             'autorun!' => \$self->{_autorun},
188             'close!' => \$self->{_close},
189             'd|debug+' => \$self->{_debug},
190             'e|empty=s' => \$self->{_null_value},
191 0     0     'i|input=s' => sub { $self->parse_io_option('input', @_); },
192             'log!' => \$self->{_logprog},
193 0     0     'o|output=s' => sub { $self->parse_io_option('output', @_); },
194             'r|relaxed-errors!' => \$self->{_relaxed_errors},
195             'v|invert-match!' => \$self->{_invert_match}
196 0 0         ) or pod2usage(2);
197 0           push (@{$self->{_arg_cols}}, @arg_cols);
  0            
198             }
199              
200             =head2 setup
201              
202             $filter->setup();
203              
204             Internal: setup, parse headers.
205              
206             =cut
207              
208             sub setup ($) {
209 0     0 1   my($self) = @_;
210              
211 0           $self->finish_io_option('input', -comment_handler => $self->create_pass_comments_sub);
212 0           my $read_fastpath_sub = $self->{_in}->fastpath_sub();
213              
214 0           my @new_arg_cols = ();
215 0 0         if ($self->{_invert_match}) {
216 0           my %bad_cols;
217 0           foreach (@{$self->{_arg_cols}}) {
  0            
218 0           my($badf) = $self->{_in}->col_to_i($_);
219 0 0         if (!defined($badf)) {
220             die $self->{_prog} . ": unknown column ``$_'' for omission.\n"
221 0 0         if (!$self->{_relaxed_errors});
222             # skip it if relaxed
223 0           next;
224             };
225 0           my($badn) = $self->{_in}->i_to_col($badf);
226 0           $bad_cols{$badn} = 1;
227             };
228             # rebuild list from survivors
229 0           foreach (@{$self->{_in}->cols}) {
  0            
230 0 0         push(@new_arg_cols, $_) if (!$bad_cols{$_});
231             };
232             } else {
233             # convert any numeric colnames to names
234 0           foreach (@{$self->{_arg_cols}}) {
  0            
235             push(@new_arg_cols, defined($self->{_in}->col_to_i($_)) ?
236 0 0         $self->{_in}->i_to_col($self->{_in}->col_to_i($_)) :
237             $_);
238             };
239             };
240 0           @{$self->{_arg_cols}} = @new_arg_cols;
  0            
241              
242             #
243             # setup conversion
244             #
245 0           my($copy_code) = "";
246 0           my(%new_colnames);
247 0           for my $out_coli (0..$#{$self->{_arg_cols}}) {
  0            
248 0           my $colname = $self->{_arg_cols}[$out_coli];
249             die $self->{_prog} . ": duplicate colname $colname\n"
250 0 0         if (defined($new_colnames{$colname}));
251 0           $new_colnames{$colname} = $out_coli;
252 0           my $in_coli = $self->{_in}->col_to_i($colname);
253 0 0         if (defined($in_coli)) {
    0          
254 0           $copy_code .= '$nf['.$out_coli.'] = $fref->['.$in_coli.'];' . "\n";
255             } elsif (!defined($self->{_null_value})) {
256 0           die ($self->{_prog} . ": creating new column ``$colname'' without specifying null value.\n");
257             } else {
258 0           $copy_code .= '$nf['.$out_coli."] = '" . $self->{_null_value} . "';\n";
259             };
260             };
261              
262             #
263             # setup output
264             #
265 0           $self->finish_io_option('output', -clone => $self->{_in}, -cols => \@{$self->{_arg_cols}});
  0            
266 0           my $write_fastpath_sub = $self->{_out}->fastpath_sub();
267              
268             #
269             # write the loop
270             #
271             # Since perl5 doesn't cache eval, eval the whole loop.
272             #
273             # This is very hairy. Use the eval to pull in the copy code,
274             # and wrap it in an anon subroutine that we store away.
275             # We have to do all this HERE, rather than in run,
276             # because $read_fastpath_sub is local to here.
277             #
278             {
279 0           my $loop_sub;
  0            
280 0           my $loop_sub_code = q'
281             $loop_sub = sub {
282             my $fref;
283             my @nf;
284             while ($fref = &$read_fastpath_sub()) {
285             ' . $copy_code . q'
286             &$write_fastpath_sub(\@nf);
287             };
288             };
289             ';
290 0           eval $loop_sub_code;
291 0 0         $@ && die $self->{_prog} . ": internal eval error: $@.\n";
292 0           $self->{_loop_sub} = $loop_sub;
293             }
294             }
295              
296             =head2 run
297              
298             $filter->run();
299              
300             Internal: run over all data rows.
301              
302             =cut
303             sub run ($) {
304 0     0 1   my($self) = @_;
305 0           &{$self->{_loop_sub}}();
  0            
306             }
307              
308             =head2 finish
309              
310             $filter->finish();
311              
312             Internal: write trailer.
313              
314             =cut
315             sub finish ($) {
316 0     0 1   my($self) = @_;
317              
318             $self->{_out}->write_comment($self->{_prog} . "\'s code: " . code_prettify($self->{_loop_code}))
319 0 0         if ($self->{_debug});
320 0           $self->SUPER::finish();
321             }
322              
323             =head1 AUTHOR and COPYRIGHT
324              
325             Copyright (C) 1991-2015 by John Heidemann
326              
327             This program is distributed under terms of the GNU general
328             public license, version 2. See the file COPYING
329             with the distribution for details.
330              
331             =cut
332              
333             1;
334