File Coverage

blib/lib/TableDataRole/Source/CSVInFiles.pm
Criterion Covered Total %
statement 83 92 90.2
branch 16 28 57.1
condition n/a
subroutine 12 13 92.3
pod 2 9 22.2
total 113 142 79.5


line stmt bran cond sub pod time code
1             package TableDataRole::Source::CSVInFiles;
2              
3 1     1   483 use 5.010001;
  1         3  
4 1     1   5 use strict;
  1         2  
  1         22  
5 1     1   5 use warnings;
  1         2  
  1         24  
6              
7 1     1   5 use Role::Tiny;
  1         2  
  1         9  
8              
9             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
10             our $DATE = '2023-06-14'; # DATE
11             our $DIST = 'TableDataRoles-Standard'; # DIST
12             our $VERSION = '0.016'; # VERSION
13              
14             with 'TableDataRole::Spec::Basic';
15              
16             sub new {
17 1     1 1 1166 require Text::CSV_XS;
18              
19 1         21326 my ($class, %args) = @_;
20              
21 1         4 my $fhs = [];
22 1 50       5 if (defined(my $filenames = delete $args{filenames})) {
    0          
23 1         5 for my $filename (@$filenames) {
24 3 50       114 open my $fh, "<", $filename
25             or die "Can't open file '$filename': $!";
26 3         17 push @$fhs, $fh;
27             }
28             } elsif (defined($fhs = delete $args{filehandles})) {
29             } else {
30 0         0 die "Please specify 'filenames' or 'filehandles'";
31             }
32 1 50       6 @$fhs or die "Please supply at least one filename/filehandle";
33 1 50       6 die "Unknown argument(s): ". join(", ", sort keys %args)
34             if keys %args;
35              
36 1         8 my $csv_parser = Text::CSV_XS->new({binary=>1});
37              
38 1         124 my $files = [];
39 1         2 my $columns;
40 1         4 for my $fh (@$fhs) {
41 3         9 my $fhpos_data_begin = tell $fh;
42 3 50       93 $columns = $csv_parser->getline($fh)
43             or die "Can't read columns from first row of CSV file";
44 3         152 my $fhpos_datarow_begin = tell $fh;
45 3         15 push @$files, {
46             fh => $fh,
47             fhpos_data_begin => $fhpos_data_begin,
48             fhpos_datarow_begin => $fhpos_datarow_begin,
49             };
50             }
51             bless {
52 1         25 files => $files,
53             csv_parser => $csv_parser,
54             columns => $columns,
55             file_pos => 0, # which file are we at
56             pos => 0, # iterator
57             }, $class;
58             }
59              
60             sub as_csv {
61 1     1 1 8 my $self = shift;
62              
63 1         3 my $res = "";
64 1         3 for my $i (0 .. $#{$self->{files}}) {
  1         10  
65 3         10 my $file = $self->{files}[$i];
66 3         7 my $fh = $file->{fh};
67 3         6 my $oldpos = tell $fh;
68 3 100       37 seek $fh, ($i ? $file->{fhpos_datarow_begin} : $file->{fhpos_data_begin}), 0;
69 3         15 local $/;
70 3         64 $res .= scalar <$fh>;
71             }
72 1         8 $self->{pos} = 0;
73 1         5 $self->{file_pos} = 0;
74 1         10 $res;
75             }
76              
77             sub get_column_count {
78 1     1 0 3 my $self = shift;
79              
80 1         3 scalar @{ $self->{columns} };
  1         4  
81             }
82              
83             sub get_column_names {
84 1     1 0 2 my $self = shift;
85 1 50       4 wantarray ? @{ $self->{columns} } : $self->{columns};
  1         10  
86             }
87              
88             sub has_next_item {
89 16     16 0 61 my $self = shift;
90 16         25 my $files = $self->{files};
91 16         26 my $seek = 0;
92 16         20 while (1) {
93 18         32 my $file = $self->{files}[$self->{file_pos}];
94 18         26 my $fh = $file->{fh};
95 18 100       36 if ($seek) {
96 2         15 seek $fh, $file->{fhpos_datarow_begin}, 0;
97 2         5 $seek = 0;
98             }
99 18 100       99 return 1 unless eof($fh);
100 3 100       10 return 0 if $self->{file_pos} >= $#{$self->{files}};
  3         15  
101 2         5 $self->{file_pos}++;
102 2         5 $seek++;
103             }
104             }
105              
106             sub get_next_item {
107 19     19 0 98 my $self = shift;
108 19         36 my $files = $self->{files};
109 19         27 my $seek = 0;
110 19         29 while (1) {
111 19         32 my $file = $self->{files}[$self->{file_pos}];
112 19         28 my $fh = $file->{fh};
113 19 50       42 if ($seek) {
114 0         0 seek $fh, $file->{fhpos_datarow_begin}, 0;
115 0         0 $seek = 0;
116             }
117 19 50       63 unless (eof($fh)) {
118 19         428 my $row = $self->{csv_parser}->getline($fh);
119 19         504 $self->{pos}++;
120 19         57 return $row;
121             }
122 0 0       0 die "StopIteration" if $self->{file_pos} >= $#{$self->{files}};
  0         0  
123 0         0 $self->{file_pos}++;
124 0         0 $seek++;
125             }
126             }
127              
128             sub get_next_row_hashref {
129 2     2 0 4 my $self = shift;
130 2         6 my $row = $self->get_next_item;
131 2         6 +{ map {($self->{columns}[$_] => $row->[$_])} 0..$#{$self->{columns}} };
  6         32  
  2         6  
132             }
133              
134             sub get_iterator_pos {
135 0     0 0 0 my $self = shift;
136 0         0 $self->{pos};
137             }
138              
139             sub reset_iterator {
140 3     3 0 21 my $self = shift;
141 3         6 $self->{file_pos} = 0;
142 3         7 my $fh = $self->{files}[0]{fh};
143 3         38 seek $fh, $self->{files}[0]{fhpos_datarow_begin}, 0;
144 3         13 $self->{pos} = 0;
145             }
146              
147             1;
148             # ABSTRACT: Role to access table data from CSV in a set of files/filehandles
149              
150             __END__