File Coverage

blib/lib/Fsdb/Filter/dbcolsplittocols.pm
Criterion Covered Total %
statement 18 72 25.0
branch 0 20 0.0
condition n/a
subroutine 6 15 40.0
pod 5 5 100.0
total 29 112 25.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             #
4             # dbcolsplittocols.pm
5             # Copyright (C) 1991-2017 by John Heidemann
6             #
7             # This program is distributed under terms of the GNU general
8             # public license, version 2. See the file COPYING
9             # in $dblibdir for details.
10             #
11              
12             package Fsdb::Filter::dbcolsplittocols;
13              
14             =head1 NAME
15              
16             dbcolsplittocols - split an existing column into multiple new columns
17              
18             =head1 SYNOPSIS
19              
20             dbcolsplittocols [-E] [-C ElementSeparator] column
21              
22             =head1 DESCRIPTION
23              
24             Create new columns by splitting an existing column.
25             The fragments of the column are each divided by ElementSeparator
26             (default is underscore).
27              
28             This command is the opposite of dbcolmerge.
29             Names of the new columns are given by splitting the name
30             of the existing column. dbcolrename may be useful
31             to set column names.
32              
33              
34             =head1 OPTIONS
35              
36             =over 4
37              
38             =item B<-C S> or B<--element-separator S>
39              
40             Specify the separator I used to join columns.
41             Usually a signle character, it can also be a regular expression
42             (so, for example, [,_] matches either , or _ as an element separator.)
43             (Defaults to a single underscore.)
44              
45             =item B<-E> or B<--enumerate>
46              
47             Enumerate output columns: rather than assuming the column name uses
48             the element separator, we keep it whole and fill in with indexes
49             starting from 0.
50             (Not currently implemented, but planned. See
51             L.)
52              
53             =item B<-N> on B<--new-name>
54              
55             Specify the names of the new columns
56             as a I separated list.
57             (Default is to apply the separator to the name of the column that is being split.)
58              
59             By default, column C will split to columns a and b.
60             If the column is given as ab with option C<-N 'a b'>,
61             one will get the same result.
62              
63             =item B<-E> or B<--enumerate>
64              
65             Enumerate output columns: rather than assuming the column name uses
66             the element separator, we keep it whole and fill in with indexes
67             starting from 0.
68             (Not currently implemented, but planned. See
69             L.)
70              
71             =back
72              
73             =for comment
74             begin_standard_fsdb_options
75              
76             This module also supports the standard fsdb options:
77              
78             =over 4
79              
80             =item B<-d>
81              
82             Enable debugging output.
83              
84             =item B<-i> or B<--input> InputSource
85              
86             Read from InputSource, typically a file name, or C<-> for standard input,
87             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
88              
89             =item B<-o> or B<--output> OutputDestination
90              
91             Write to OutputDestination, typically a file name, or C<-> for standard output,
92             or (if in Perl) a IO::Handle, Fsdb::IO or Fsdb::BoundedQueue objects.
93              
94             =item B<--autorun> or B<--noautorun>
95              
96             By default, programs process automatically,
97             but Fsdb::Filter objects in Perl do not run until you invoke
98             the run() method.
99             The C<--(no)autorun> option controls that behavior within Perl.
100              
101             =item B<--header> H
102              
103             Use H as the full Fsdb header, rather than reading a header from
104             then input.
105              
106             =item B<--help>
107              
108             Show help.
109              
110             =item B<--man>
111              
112             Show full manual.
113              
114             =back
115              
116             =for comment
117             end_standard_fsdb_options
118              
119              
120             =head1 SAMPLE USAGE
121              
122             =head2 Input:
123              
124             #fsdb first_last
125             John_Heidemann
126             Greg_Johnson
127             Root
128             # this is a simple database
129             # | dbcolrename fullname first_last
130             # | /home/johnh/BIN/DB/dbcol first_last
131              
132             =head2 Command:
133              
134             cat data.fsdb | dbcolsplittocols first_last
135              
136             =head2 Output:
137              
138             #fsdb first_last first last
139             John_Heidemann John Heidemann
140             Greg_Johnson Greg Johnson
141             Root Root
142             # this is a simple database
143             # | dbcolrename fullname first_last
144             # | /home/johnh/BIN/DB/dbcol first_last
145             # | /home/johnh/BIN/DB/dbcolsplittocols first_last
146              
147             =head1 SEE ALSO
148              
149             L.
150             L.
151             L.
152             L.
153              
154              
155             =head1 CLASS FUNCTIONS
156              
157             =cut
158              
159             @ISA = qw(Fsdb::Filter);
160             $VERSION = 2.0;
161              
162 1     1   4275 use strict;
  1         2  
  1         27  
163 1     1   5 use Pod::Usage;
  1         2  
  1         70  
164 1     1   7 use Carp;
  1         2  
  1         41  
165              
166 1     1   6 use Fsdb::Filter;
  1         7  
  1         17  
167 1     1   5 use Fsdb::IO::Reader;
  1         1  
  1         21  
168 1     1   4 use Fsdb::IO::Writer;
  1         3  
  1         695  
169              
170              
171             =head2 new
172              
173             $filter = new Fsdb::Filter::dbcolsplittocols(@arguments);
174              
175             Create a new dbcolsplittocols object, taking command-line arguments.
176              
177             =cut
178              
179             sub new ($@) {
180 0     0 1   my $class = shift @_;
181 0           my $self = $class->SUPER::new(@_);
182 0           bless $self, $class;
183 0           $self->set_defaults;
184 0           $self->parse_options(@_);
185 0           $self->SUPER::post_new();
186 0           return $self;
187             }
188              
189              
190             =head2 set_defaults
191              
192             $filter->set_defaults();
193              
194             Internal: set up defaults.
195              
196             =cut
197              
198             sub set_defaults ($) {
199 0     0 1   my($self) = @_;
200 0           $self->SUPER::set_defaults();
201 0           $self->{_elem_separator} = '_';
202 0           $self->{_enumerate} = undef;
203 0           $self->{_target_column} = undef;
204 0           $self->{_header} = undef;
205 0           $self->{_destination_column_list} = undef;
206             }
207              
208             =head2 parse_options
209              
210             $filter->parse_options(@ARGV);
211              
212             Internal: parse command-line arguments.
213              
214             =cut
215              
216             sub parse_options ($@) {
217 0     0 1   my $self = shift @_;
218              
219 0           my(@argv) = @_;
220             $self->get_options(
221             \@argv,
222 0     0     'help|?' => sub { pod2usage(1); },
223 0     0     'man' => sub { pod2usage(-verbose => 2); },
224             'autorun!' => \$self->{_autorun},
225             'close!' => \$self->{_close},
226             'C|element-separator=s' => \$self->{_elem_separator},
227             'd|debug+' => \$self->{_debug},
228             'E|enumerate!' => \$self->{_enumerate},
229             'header=s' => \$self->{_header},
230 0     0     'i|input=s' => sub { $self->parse_io_option('input', @_); },
231             'log!' => \$self->{_logprog},
232             'N|new-name=s' => \$self->{_destination_column_list},
233 0     0     'o|output=s' => sub { $self->parse_io_option('output', @_); },
234 0 0         ) or pod2usage(2);
235 0           $self->parse_target_column(\@argv);
236             }
237              
238             =head2 setup
239              
240             $filter->setup();
241              
242             Internal: setup, parse headers.
243              
244             =cut
245              
246             sub setup($) {
247 0     0 1   my($self) = @_;
248              
249 0           my(@in_options) = (-comment_handler => $self->create_pass_comments_sub);
250 0 0         push(@in_options, -header => $self->{_header}) if (defined($self->{_header}));
251 0           $self->finish_io_option('input', @in_options);
252              
253 0 0         pod2usage(2) if (!defined($self->{_target_column}));
254              
255 0           $self->{_target_coli} = $self->{_in}->col_to_i($self->{_target_column});
256             croak $self->{_prog} . ": target column " . $self->{_target_column} . " is not in input stream.\n"
257 0 0         if (!defined($self->{_target_coli}));
258              
259             # Sanity check user's input to avoid injection attacks.
260             croak $self->{_prog} . ": bad element separator.\n"
261 0 0         if ($self->{_elem_separator} =~ /\'/);
262              
263 0           $self->finish_io_option('output', -clone => $self->{_in}, -outputheader => 'delay');
264 0           my(@new_columns);
265 0 0         if ($self->{_enumerate}) {
266             # xxx: need to estimate how many we need, but we can't do that.
267 0           croak $self->{_prog} . ": enumeration is not currently supported\n";
268             } else {
269 0 0         if (defined($self->{_destination_column_list})) {
270 0           @new_columns = split(/[\s+]/, $self->{_destination_column_list});
271             } else {
272 0           @new_columns = split(/$self->{_elem_separator}/, $self->{_target_column});
273             };
274             };
275 0           my @new_colis = ();
276              
277             #
278             # Write the code to do the split, and check stuff on the way.
279             #
280             my $code = 'my @p = split(/' . quotemeta($self->{_elem_separator}) . '/, $fref->[' . $self->{_target_coli} . ']);' . "\n" .
281 0           'push(@p, (' . "'" . $self->{_empty} . "') x (" . $#new_columns . ' - $#p)) if ($#p < ' . $#new_columns . ");\n";
282 0           my $new_ci = 0;
283 0           foreach (@new_columns) {
284             $self->{_out}->col_create($_)
285 0 0         or croak $self->{_prog} . ": cannot create column " . $_ . " (maybe it already existed?)\n";
286 0           my $i = $self->{_out}->col_to_i($_);
287 0           push(@new_colis, $i);
288 0           $code .= '$fref->[' . $i . '] = $p[' . $new_ci . '];' . "\n";
289 0           $new_ci++;
290             };
291 0           $self->{_split_code} = $code;
292             }
293              
294             =head2 run
295              
296             $filter->run();
297              
298             Internal: run over each rows.
299              
300             =cut
301             sub run ($) {
302 0     0 1   my($self) = @_;
303              
304 0           my $read_fastpath_sub = $self->{_in}->fastpath_sub();
305 0           my $write_fastpath_sub = $self->{_out}->fastpath_sub();
306              
307             my($loop) = q'{
308             my $fref;
309             while ($fref = &$read_fastpath_sub()) {
310 0           ' . $self->{_split_code} . q'
311             &$write_fastpath_sub($fref);
312             };
313             }';
314 0 0         print $loop if ($self->{_debug});
315 0           eval $loop;
316 0 0         $@ && croak $self->{_prog} . ": interal eval error: $@.\n";
317             }
318              
319              
320             =head1 AUTHOR and COPYRIGHT
321              
322             Copyright (C) 1991-2017 by John Heidemann
323              
324             This program is distributed under terms of the GNU general
325             public license, version 2. See the file COPYING
326             with the distribution for details.
327              
328             =cut
329              
330             1;