File Coverage

blib/lib/PDL/IO/CSV.pm
Criterion Covered Total %
statement 228 485 47.0
branch 68 278 24.4
condition 16 88 18.1
subroutine 34 39 87.1
pod 4 4 100.0
total 350 894 39.1


line stmt bran cond sub pod time code
1             package PDL::IO::CSV;
2              
3 3     3   374742 use strict;
  3         5  
  3         73  
4 3     3   9 use warnings;
  3         4  
  3         206  
5              
6             require Exporter;
7             our @ISA = qw(Exporter);
8             our @EXPORT_OK = qw(rcsv1D rcsv2D wcsv1D wcsv2D);
9             our %EXPORT_TAGS = (all => \@EXPORT_OK);
10              
11             our $VERSION = '0.009';
12              
13 3     3   12 use Config;
  3         5  
  3         174  
14 3 50   3   11 use constant NO64BITINT => ($Config{ivsize} < 8) ? 1 : 0;
  3         2  
  3         468  
15 3 50   3   11 use constant NODATETIME => eval { require PDL::DateTime; require Time::Moment; 1 } ? 0 : 1;
  3         3  
  3         4  
  3         690  
  0         0  
  0         0  
16 3 50   3   11 use constant DEBUG => $ENV{PDL_IO_CSV_DEBUG} ? 1 : 0;
  3         3  
  3         138  
17              
18 3     3   511 use PDL;
  3         17  
  3         12  
19 3     3   165599 use Text::CSV_XS;
  3         21600  
  3         152  
20 3     3   18 use Scalar::Util qw(looks_like_number openhandle blessed);
  3         3  
  3         195  
21 3     3   1362 use POSIX qw();
  3         11727  
  3         61  
22 3     3   1364 use Time::Piece;
  3         20318  
  3         13  
23              
24 3     3   175 use Carp;
  3         4  
  3         192  
25             $Carp::Internal{ (__PACKAGE__) }++;
26              
27             sub import {
28 3     3   29 my $package = shift;
29             {
30 3     3   12 no strict 'refs';
  3         5  
  3         4113  
  3         3  
31 3 100       8 *{'PDL::wcsv2D'} = \&wcsv2D if grep { /^(:all|wcsv2D)$/ } @_;
  2         10  
  2         17  
32 3 100       6 *{'PDL::wcsv1D'} = \&wcsv1D if grep { /^(:all|wcsv1D)$/ } @_;
  2         18  
  2         8  
33             }
34 3 100       298 __PACKAGE__->export_to_level(1, $package, @_) if @_;
35             }
36              
37             my %pck = (
38             byte => "C",
39             short => "s",
40             ushort => "S",
41             long => "l",
42             longlong => "q",
43             float => "f",
44             double => "d",
45             );
46              
47             sub wcsv1D {
48 0     0 1 0 my ($fh, $O, $C) = _proc_wargs('1D', @_);
49              
50 0         0 my $cols = 0;
51 0         0 my $rows = 0;
52 0         0 my @c_pdl;
53             my @c_rows;
54 0         0 my @c_type;
55 0         0 my @c_size;
56 0         0 my @c_pack;
57 0         0 my @c_dataref;
58 0         0 my @c_offset;
59 0         0 my @c_max_offset;
60 0         0 my @c_bad;
61              
62 0         0 my $bad2empty = $O->{bad2empty};
63              
64 0   0     0 while (blessed $_[0] && $_[0]->isa('PDL')) {
65 0         0 $c_pdl[$cols] = shift;
66 0 0       0 croak "FATAL: wcsv1D() expects 1D piddles" unless $c_pdl[$cols]->ndims == 1;
67 0         0 $c_size[$cols] = PDL::Core::howbig($c_pdl[$cols]->get_datatype);
68 0         0 $c_dataref[$cols] = $c_pdl[$cols]->get_dataref;
69 0         0 $c_offset[$cols] = 0;
70 0         0 my $type = $c_pdl[$cols]->type;
71 0         0 my $dim = $c_pdl[$cols]->dim(0);
72 0         0 $c_pack[$cols] = $pck{$type};
73 0 0 0     0 croak "FATAL: your perl does not support 64bitint (avoid using type longlong)" if $c_pack[$cols] eq 'q' && NO64BITINT;
74 0         0 $c_max_offset[$cols] = $c_size[$cols] * $dim;
75 0 0       0 $rows = $dim if $rows < $dim;
76 0 0 0     0 if ($bad2empty && $c_pdl[$cols]->check_badflag) {
77 0         0 my $b = pdl($type, 1)->setbadif(1);
78 0         0 my $d = $b->get_dataref;
79 0         0 $c_bad[$cols] = substr($$d, 0, $c_size[$cols]); # raw bytes representind BAD value
80             }
81 0         0 $cols++;
82             }
83              
84 0 0       0 my $csv = Text::CSV_XS->new($C) or croak "" . Text::CSV_XS->error_diag();
85 0 0       0 if ($O->{header}) {
86 0         0 my $count = scalar @{$O->{header}};
  0         0  
87 0 0       0 croak "FATAL: wrong header (expected $cols items, got $count)" if $cols != $count;
88 0         0 $csv->print($fh, $O->{header});
89             }
90 0         0 for my $r (0..$rows-1) {
91 0         0 my @v = ('') x $cols;
92 0         0 for my $c (0..$cols-1) {
93 0 0       0 if ($c_max_offset[$c] >= $c_offset[$c]) {
94 0 0 0     0 if ($bad2empty && $c_bad[$c]) {
95 0         0 my $v = substr(${$c_dataref[$c]}, $c_offset[$c], $c_size[$c]);
  0         0  
96 0 0       0 if ($v ne $c_bad[$c]) {
97 0         0 $v[$c] = unpack($c_pack[$c], $v);
98 0 0       0 $v[$c] = PDL::DateTime::ll2dt($v[$c]) if ref $c_pdl[$c] eq 'PDL::DateTime';
99             }
100             }
101             else {
102 0         0 my $v = substr(${$c_dataref[$c]}, $c_offset[$c], $c_size[$c]);
  0         0  
103 0         0 $v[$c] = unpack($c_pack[$c], $v);
104 0 0       0 $v[$c] = PDL::DateTime::ll2dt($v[$c]) if ref $c_pdl[$c] eq 'PDL::DateTime';
105             }
106             }
107 0         0 $c_offset[$c] += $c_size[$c];
108             }
109 0         0 $csv->print($fh, \@v);
110             }
111             #XXX close $fh;
112             }
113              
114             sub wcsv2D {
115 0     0 1 0 my $pdl = shift;
116 0         0 my ($fh, $O, $C) = _proc_wargs('2D', @_);
117              
118 0 0       0 croak "FATAL: wcsv2D() expects 2D piddle" unless $pdl->ndims == 2;
119 0         0 my $p = $pdl->transpose;
120              
121 0         0 my ($cols, $rows) = $p->dims;
122 0         0 my $type = $p->type;
123 0         0 my $size = PDL::Core::howbig($p->get_datatype);
124 0         0 my $packC = $pck{$type} . "[$cols]";
125 0         0 my $pack1 = $pck{$type};
126 0 0 0     0 croak "FATAL: your perl does not support 64bitint (avoid using type longlong)" if $pck{$type} eq 'q' && NO64BITINT;
127 0         0 my $dataref = $p->get_dataref;
128 0         0 my $offset = 0;
129 0         0 my $colsize = $size * $cols;
130 0         0 my $max_offset = $colsize * ($rows - 1);
131 0         0 my $bad;
132 0 0 0     0 if ($O->{bad2empty} && $p->check_badflag) {
133 0         0 my $b = pdl($type, 1)->setbadif(1);
134 0         0 my $d = $b->get_dataref;
135 0         0 $bad = substr($$d, 0, $size); # raw bytes representind BAD value
136             }
137              
138 0 0       0 my $csv = Text::CSV_XS->new($C) or croak "" . Text::CSV_XS->error_diag();
139 0 0       0 if ($O->{header}) {
140 0         0 my $n = scalar @{$O->{header}};
  0         0  
141 0 0       0 croak "FATAL: wrong header (expected $cols items, got $n)" if $cols != $n;
142 0         0 $csv->print($fh, $O->{header});
143             }
144 0         0 while ($offset <= $max_offset) {
145 0 0       0 if (defined $bad) {
146 0 0       0 my @v = map { my $v = substr($$dataref, $offset + $_*$size, $size); $v eq $bad ? '' : unpack($pack1, $v) } (0..$cols-1);
  0         0  
  0         0  
147 0         0 $csv->print($fh, \@v);
148             }
149             else {
150 0         0 my @v = unpack($packC, substr($$dataref, $offset, $colsize));
151 0         0 $csv->print($fh, \@v);
152             }
153 0         0 $offset += $colsize;
154             }
155             #XXX close $fh;
156             }
157              
158             sub rcsv1D {
159 2     2 1 978 my ($fh, $coli, $O, $C) = _proc_rargs('1D', @_);
160              
161 2         3 my ($c_type, $c_pack, $c_sizeof, $c_pdl, $c_bad, $c_dataref, $c_idx, $c_dt, $allocated, $cols); # initialize after we get 1st line
162              
163 2 50       15 my $csv = Text::CSV_XS->new($C) or croak "" . Text::CSV_XS->error_diag();
164 2         183 my $processed = 0;
165 2         3 my $finished = 0;
166 2         4 my $chunk = $O->{fetch_chunk};
167 2         11 my $empty2bad = $O->{empty2bad};
168 2         5 my $text2bad = $O->{text2bad};
169 2         4 my $dec_comma = $O->{decimal_comma};
170              
171 2 50       8 warn "Fetching 1D " . _dbg_msg($O, $C) . "\n" if $O->{debug};
172             # skip headers
173 2         3 my $headerline;
174             my $headerdetection;
175 2 50 33     27 if (looks_like_number($O->{header})) {
    50          
176 0 0       0 if ($O->{header} == 1) {
177             # get the header line (only if skipping exactly 1 line)
178 0         0 $headerline = $csv->getline($fh);
179             }
180             else {
181 0         0 $csv->getline($fh) for (1..$O->{header});
182             }
183             }
184             elsif ($O->{header} && $O->{header} eq 'auto') {
185 2         6 $headerdetection = 1;
186             }
187              
188 2         6 while (!$finished) {
189 2         4 my $rows = 0;
190 2         22 my @bytes;
191             my $r;
192 2         9 while ($rows < $chunk) {
193 3     1   93 my $r = $csv->getline($fh);
  1         5  
  1         2  
  1         37  
194 3 50       84 if (defined $r) {
195 3 50       6 if (defined $headerdetection) {
196 3         4 my $numeric = 0;
197 3         6 for (@$r) {
198 11 100       22 if (looks_like_number($_)) {
199 8         8 $numeric++;
200             }
201 0         0 elsif (!NODATETIME) {
202             my $v = $O->{strptime} ? _strptime($_, $O->{strptime}) : PDL::DateTime::dt2ll($_);
203             $numeric++ if defined $v;
204             }
205             }
206 3 100       7 if ($numeric == 0) {
207             # no numeric values found => skip this line but keep it as a potential header
208 1         1 $headerline = $r;
209 1         3 next;
210             }
211 2         5 $headerdetection = undef;
212             }
213 2 50       9 unless (defined $c_type) {
214 2         8 ($c_type, $c_pack, $c_sizeof, $c_pdl, $c_bad, $c_dataref, $c_idx, $c_dt, $allocated, $cols) = _init_1D($coli, $r, $O);
215 0 0       0 warn "Initialized size=$allocated, cols=$cols, type=".join(",",@$c_type)."\n" if $O->{debug};
216             }
217 0 0       0 if ($dec_comma) {
218 0 0       0 for (@$r) { s/,/./ if defined $_ };
  0         0  
219             }
220 0 0       0 if ($empty2bad) {
221 0 0       0 if (defined $coli) {
222 0         0 for (0..$cols-1) {
223 0         0 my $i = $coli->[$_];
224 0 0       0 unless (defined $r->[$i]) { $r->[$i] = $c_bad->[$_]; $c_pdl->[$_]->badflag(1) }
  0         0  
  0         0  
225             }
226             }
227             else {
228 0         0 for (0..$cols-1) {
229 0 0       0 unless (defined $r->[$_]) { $r->[$_] = $c_bad->[$_]; $c_pdl->[$_]->badflag(1) }
  0         0  
  0         0  
230             }
231             }
232             }
233 0 0       0 if (defined $c_dt) {
234 0         0 for (0..$cols-1) {
235 0 0       0 next unless defined $c_dt->[$_];
236 0 0       0 my $v = $c_dt->[$_] ne 'datetime' ? _strptime($r->[$_], $c_dt->[$_]) : PDL::DateTime::dt2ll($r->[$_]);
237 0 0       0 if (defined $v) {
238 0         0 $r->[$_] = $v;
239             }
240             else {
241 0         0 $r->[$_] = $c_bad->[$_];
242 0         0 $c_pdl->[$_]->badflag(1);
243             }
244             }
245             }
246 0 0       0 if ($text2bad) {
247 0 0       0 if (defined $coli) {
248 0         0 for (0..$cols-1) {
249 0         0 my $i = $coli->[$_];
250 0 0       0 unless (looks_like_number($r->[$i])) { $r->[$i] = $c_bad->[$_]; $c_pdl->[$_]->badflag(1) }
  0         0  
  0         0  
251             }
252             }
253             else {
254 0         0 for (0..$cols-1) {
255 0 0       0 unless (looks_like_number($r->[$_])) { $r->[$_] = $c_bad->[$_]; $c_pdl->[$_]->badflag(1) }
  0         0  
  0         0  
256             }
257             }
258             }
259 0 0       0 if (defined $coli) { # only selected columns
260 3     3   16 no warnings 'pack'; # intentionally disable all pack related warnings
  3         4  
  3         108  
261 3     3   10 no warnings 'numeric'; # disable: Argument ??? isn't numeric in pack
  3         3  
  3         125  
262 3     3   15 no warnings 'uninitialized'; # disable: Use of uninitialized value in pack
  3         3  
  3         142  
263 0         0 $bytes[$_] .= pack($c_pack->[$_], $r->[$coli->[$_]]) for (0..$cols-1);
264             }
265             else { # all columns
266 3     3   10 no warnings 'pack'; # intentionally disable all pack related warnings
  3         3  
  3         69  
267 3     3   9 no warnings 'numeric'; # disable: Argument ??? isn't numeric in pack
  3         3  
  3         55  
268 3     3   9 no warnings 'uninitialized'; # disable: Use of uninitialized value in pack
  3         3  
  3         2004  
269 0         0 $bytes[$_] .= pack($c_pack->[$_], $r->[$_]) for (0..$cols-1);
270             }
271 0         0 $rows++;
272             }
273             else {
274 0         0 $finished = 1;
275 0         0 last;
276             }
277             }
278 0 0       0 if ($rows > 0) {
279 0         0 $processed += $rows;
280 0 0       0 if ($allocated < $processed) {
281 0         0 $allocated += $O->{reshape_inc};
282 0 0       0 warn "Reshape to: '$allocated'\n" if $O->{debug};
283 0         0 for (0..$cols-1) {
284 0         0 $c_pdl->[$_]->reshape($allocated);
285 0         0 $c_dataref->[$_] = $c_pdl->[$_]->get_dataref;
286             }
287             }
288 0         0 for my $ci (0..$cols-1) {
289 0         0 my $len = length $bytes[$ci];
290 0         0 my $expected_len = $c_sizeof->[$ci] * $rows;
291 0 0       0 croak "FATAL: len mismatch $len != $expected_len" if $len != $expected_len;
292 0         0 substr(${$c_dataref->[$ci]}, $c_idx->[$ci], $len) = $bytes[$ci];
  0         0  
293 0         0 $c_idx->[$ci] += $expected_len;
294             }
295             }
296             }
297              
298             #XXX close $fh;
299 0 0       0 if (ref $c_pdl eq 'ARRAY') {
300 0 0       0 if ($processed != $allocated) {
301 0 0       0 warn "Reshape to: '$processed' (final)\n" if $O->{debug};
302 0         0 $c_pdl->[$_]->reshape($processed) for (0..$cols-1);
303             }
304 0         0 $c_pdl->[$_]->upd_data for (0..$cols-1);
305 0 0       0 if (ref $headerline eq 'ARRAY') {
306 0         0 for (0..$cols-1) {
307 0 0 0     0 $c_pdl->[$_]->hdr->{col_name} = $headerline->[$_] if $headerline->[$_] && $headerline->[$_] ne '';
308             };
309             }
310 0         0 return @$c_pdl;
311             }
312              
313 0         0 warn "rcsv1D: no data\n";
314 0         0 return undef;
315             }
316              
317             sub rcsv2D {
318 2     2 1 80 my ($fh, $coli, $O, $C) = _proc_rargs('2D', @_);
319              
320 2         2 my ($c_type, $c_pack, $c_sizeof, $c_pdl, $c_bad, $c_dataref, $allocated, $cols);
321              
322 2 50       15 my $csv = Text::CSV_XS->new($C) or croak "" . Text::CSV_XS->error_diag();
323 2         178 my $processed = 0;
324 2         2 my $c_idx = 0;
325 2         2 my $finished;
326             my $pck;
327 2         3 my $chunk = $O->{fetch_chunk};
328 2         2 my $empty2bad = $O->{empty2bad};
329 2         3 my $text2bad = $O->{text2bad};
330 2         1 my $dec_comma = $O->{decimal_comma};
331 2         3 my $bcount = 0;
332              
333 2 50       3 warn "Fetching 2D " . _dbg_msg($O, $C) . "\n" if $O->{debug};
334             # skip headers
335 2         6 $csv->getline($fh) for (1..$O->{header});
336 2         5 while (!$finished) {
337 2         1 my $bytes = '';
338 2         2 my $rows = 0;
339 2         7 while ($rows < $chunk) {
340 1     1   6 my $r = $csv->getline($fh);
  1         1  
  1         35  
  12         236  
341 12 100       314 if (defined $r) {
342 10 100       18 unless (defined $pck) {
343 2         6 ($c_type, $c_pack, $c_sizeof, $c_pdl, $c_bad, $c_dataref, $allocated, $cols) = _init_2D($coli, scalar @$r, $O);
344 2 50       7 warn "Initialized size=$allocated, cols=$cols, type=$c_type\n" if $O->{debug};
345 2         5 $pck = "$c_pack\[$cols\]";
346 2 50       5 next if $O->{header};
347             }
348 10 50       14 if ($dec_comma) {
349 0 0       0 for (@$r) { s/,/./ if defined $_ };
  0         0  
350             }
351 10 50       12 if ($empty2bad) {
352 0 0       0 if (defined $coli) {
353 0         0 for (0..$cols-1) {
354 0         0 my $i = $coli->[$_];
355 0 0       0 unless (defined $r->[$i]) { $r->[$i] = $c_bad; $c_pdl->badflag(1) }
  0         0  
  0         0  
356             }
357             }
358             else {
359 0         0 for (0..$cols-1) {
360 0 0       0 unless (defined $r->[$_]) { $r->[$_] = $c_bad; $c_pdl->badflag(1) }
  0         0  
  0         0  
361             }
362             }
363             }
364 10 50       11 if ($text2bad) {
365 0 0       0 if (defined $coli) {
366 0         0 for (0..$cols-1) {
367 0         0 my $i = $coli->[$_];
368 0 0       0 unless (looks_like_number($r->[$i])) { $r->[$i] = $c_bad; $c_pdl->badflag(1) }
  0         0  
  0         0  
369             }
370             }
371             else {
372 0         0 for (0..$cols-1) {
373 0 0       0 unless (looks_like_number($r->[$_])) { $r->[$_] = $c_bad; $c_pdl->badflag(1) }
  0         0  
  0         0  
374             }
375             }
376             }
377 10 50       10 if (defined $coli) { # only selected columns
378 3     3   17 no warnings 'pack'; # intentionally disable all pack related warnings
  3         3  
  3         117  
379 3     3   13 no warnings 'numeric'; # disable: Argument ??? isn't numeric in pack
  3         9  
  3         79  
380 3     3   13 no warnings 'uninitialized'; # disable: Use of uninitialized value in pack
  3         2  
  3         131  
381 0         0 $bytes .= pack($pck, map { $r->[$_] } @$coli);
  0         0  
382             }
383             else { # all columns
384 3     3   10 no warnings 'pack'; # intentionally disable all pack related warnings
  3         2  
  3         74  
385 3     3   9 no warnings 'numeric'; # disable: Argument ??? isn't numeric in pack
  3         3  
  3         60  
386 3     3   8 no warnings 'uninitialized'; # disable: Use of uninitialized value in pack
  3         3  
  3         4471  
387 10         46 $bytes .= pack($pck, @$r);
388             }
389 10         17 $rows++;
390             }
391             else {
392 2         3 $finished = 1;
393 2         3 last;
394             }
395             }
396 2 50       5 if ($rows > 0) {
397 2         3 $processed += $rows;
398 2 50       5 if ($allocated < $processed) {
399 0         0 $allocated += $O->{reshape_inc};
400 0 0       0 warn "Reshaping to $allocated\n" if $O->{debug};
401 0         0 $c_pdl->reshape($cols, $allocated);
402 0         0 $c_dataref = $c_pdl->get_dataref;
403             }
404 2         6 my $len = length $bytes;
405 2         3 my $expected_len = $c_sizeof * $cols * $rows;
406 2 50       4 croak "FATAL: len mismatch $len != $expected_len" if $len != $expected_len;
407 2         5 substr($$c_dataref, $c_idx, $len) = $bytes;
408 2         3 $c_idx += $len;
409             }
410             }
411              
412             #XXX close $fh;
413              
414 2 50 33     24 if (blessed $c_pdl && $c_pdl->isa('PDL')) {
415 2 50       4 if ($processed != $allocated) {
416 2 50       9 warn "Reshaping to $processed (final)\n" if $O->{debug};
417 2         6 $c_pdl->reshape($cols, $processed); # allocate the exact size
418             }
419 2         76 $c_pdl->upd_data;
420 2         8 return $c_pdl->transpose;
421             }
422              
423 0         0 warn "rcsv2D: no data\n";
424 0         0 return undef;
425             }
426              
427             sub _dbg_msg {
428 0     0   0 my ($O, $C) = @_;
429             sprintf "chunk=%s, reshape=%s, bad=%s/%s, sep_char='%s'",
430             $O->{fetch_chunk} ||= '?',
431             $O->{reshape_inc} ||= '?',
432             $O->{empty2bad} ||= '?',
433             $O->{text2bad} ||= '?',
434 0   0     0 $C->{sep_char} ||= '?';
      0        
      0        
      0        
      0        
435             }
436              
437             sub _proc_wargs {
438 0 0   0   0 my $options = ref $_[-1] eq 'HASH' ? pop : {};
439 0 0 0     0 my $filename_or_fh = !blessed $_[-1] || !$_[-1]->isa('PDL') ? pop : undef;
440 0         0 my $fn = shift;
441              
442 0         0 my $C = { %$options }; # make a copy
443              
444 0         0 my @keys = qw/ debug header bad2empty encoding /;
445 0         0 my $O = { map { $_ => delete $C->{$_} } @keys };
  0         0  
446 0 0       0 $O->{debug} = DEBUG unless defined $O->{debug};
447 0 0       0 $O->{bad2empty} = 1 unless defined $O->{bad2empty};
448 0 0       0 $O->{header} = ($fn eq '1D' ? 'auto' : 0) if !defined $O->{header};
    0          
449              
450             # explicitely set
451 0 0       0 $C->{sep_char} = ',' unless defined $C->{sep_char};
452 0 0       0 $C->{eol} = "\n" unless defined $C->{eol};
453              
454 0 0       0 if (defined $O->{header}) {
455 0 0 0     0 croak "FATAL: header should be arrayref" unless ref $O->{header} eq 'ARRAY' || $O->{header} eq 'auto';
456 0 0       0 if ($O->{header} eq 'auto') {
457 0         0 my @n;
458 0         0 my $count = 0;
459 0         0 for (@_) {
460 0         0 push @n, my $n = $_->hdr->{col_name};
461 0 0       0 $count++ if defined $n;
462             }
463 0 0       0 $O->{header} = $count > 0 ? \@n : undef;
464             }
465             }
466              
467 0         0 my $fh;
468 0 0       0 if (!defined $filename_or_fh) {
    0          
469 0         0 $fh = \*STDOUT;
470             }
471             elsif (openhandle($filename_or_fh)) {
472 0         0 $fh = $filename_or_fh;
473             }
474             else {
475 0 0       0 open $fh, ">", $filename_or_fh or croak "$filename_or_fh: $!";
476             }
477 0 0       0 binmode $fh, $O->{encoding} if $O->{encoding};
478              
479 0         0 return ($fh, $O, $C);
480             }
481              
482             sub _proc_rargs {
483 4 100   4   15 my $options = ref $_[-1] eq 'HASH' ? pop : {};
484 4         9 my ($fn, $filename_or_fh, $coli) = @_;
485              
486 4 50 33     12 croak "FATAL: invalid column ids" if defined $coli && ref $coli ne 'ARRAY';
487 4 50       9 croak "FATAL: invalid filename" unless defined $filename_or_fh;
488 4         9 my $C = { %$options }; # make a copy
489              
490             # get options related to this module the rest will be passed to Text::CSV_XS
491 4         22 my @keys = qw/ reshape_inc fetch_chunk type debug empty2bad text2bad header decimal_comma encoding detect_datetime /;
492 4         5 my $O = { map { $_ => delete $C->{$_} } @keys };
  40         53  
493 4   50     23 $O->{fetch_chunk} ||= 40_000;
494 4   50     13 $O->{reshape_inc} ||= 80_000;
495 4 100 33     26 $O->{type} ||= ($fn eq '1D' ? 'auto' : double);
496 4 100 100     84 $O->{header} ||= ($fn eq '1D' ? 'auto' : 0);
497 4 100       11 $O->{detect_datetime} = 1 unless defined $O->{detect_datetime};
498 4 100       13 if ($O->{detect_datetime} =~ /%/) {
499 1         2 $O->{strptime} = $O->{detect_datetime};
500 1         2 $O->{detect_datetime} = 1;
501             }
502 4 50       10 $O->{debug} = DEBUG unless defined $O->{debug};
503              
504             # reshape_inc cannot be lower than fetch_chunk
505 4 50       10 $O->{reshape_inc} = $O->{fetch_chunk} if $O->{reshape_inc} < $O->{fetch_chunk};
506              
507             # explicitely set column separator default
508 4 50       12 $C->{sep_char} = ',' unless defined $C->{sep_char};
509              
510             # empty2bad implies some Text::CSV_XS extra options
511 4 50       9 if ($O->{empty2bad}) {
512 0         0 $C->{blank_is_undef} = 1;
513 0         0 $C->{empty_is_undef} = 1;
514             }
515              
516 4 50 33     10 croak "FATAL: cannot use decimal_comma + sep_char ','" if $O->{decimal_comma} && $C->{sep_char} eq ',';
517              
518 4         3 my $fh;
519 4 50       13 if (openhandle($filename_or_fh)) {
520 0         0 $fh = $filename_or_fh;
521             }
522             else {
523 4 50   1   143 open $fh, "<", $filename_or_fh or croak "$filename_or_fh: $!";
  1         9  
  1         2  
  1         7  
524             }
525 4 50       978 binmode $fh, $O->{encoding} if $O->{encoding};
526              
527 4         15 return ($fh, $coli, $O, $C);
528             }
529              
530             sub _init_1D {
531 2     2   3 my ($coli, $firstline, $O) = @_;
532 2         4 my $colcount = scalar @$firstline;
533 2         2 my $cols;
534 2 50       7 if (!defined $coli) { # take all columns
535 2         4 $cols = $colcount;
536             }
537             else {
538 0         0 $cols = scalar @$coli;
539 0   0     0 ($_<0 || $_>$colcount) and croak "FATAL: invalid column '$_' (column count=$colcount)" for (@$coli);
      0        
540             }
541 2 50 33     23 croak "FATAL: invalid column count" unless $cols && $cols > 0 && $cols <= $colcount;
      33        
542              
543 2         3 my @c_type;
544             my @c_pack;
545 0         0 my @c_sizeof;
546 0         0 my @c_pdl;
547 0         0 my @c_bad;
548 0         0 my @c_dataref;
549 0         0 my @c_idx;
550              
551 2 50       9 if (ref $O->{type} eq 'ARRAY') {
552 0         0 $c_type[$_] = $O->{type}->[$_] for (0..$cols-1);
553             }
554             else {
555 2         14 $c_type[$_] = $O->{type} for (0..$cols-1);
556             }
557              
558 2         3 my @c_dt;
559 2         4 for (0..$cols-1) {
560 2 50 33     11 if (!defined $c_type[$_] || $c_type[$_] eq 'auto') {
    0          
    0          
561 2         3 $c_type[$_] = undef;
562 2 50       7 if ($O->{detect_datetime}) {
563 2         757 croak "PDL::DateTime not installed" if NODATETIME;
564 0 0       0 my $v = $O->{strptime} ? _strptime($firstline->[$_], $O->{strptime}) : PDL::DateTime::dt2ll($firstline->[$_]);
565 0 0       0 if (defined $v) {
566 0 0       0 $c_dt[$_] = $O->{strptime} ? $O->{strptime} : 'datetime';
567 0         0 $c_type[$_] = longlong;
568             }
569             }
570             }
571             elsif ($c_type[$_] eq 'datetime') {
572 0         0 croak "PDL::DateTime not installed" if NODATETIME;
573 0         0 $c_dt[$_] = 'datetime';
574 0         0 $c_type[$_] = longlong;
575             }
576             elsif ($c_type[$_] =~ /%/) {
577 0         0 $c_dt[$_] = $c_type[$_]; # strptime format
578 0         0 $c_type[$_] = longlong;
579             }
580 0 0       0 $c_type[$_] = double if !$c_type[$_];
581             }
582              
583 0         0 my $allocated = $O->{reshape_inc};
584 0         0 for (0..$cols-1) {
585 0 0       0 $c_type[$_] = double if !defined $c_type[$_];
586 0         0 $c_pack[$_] = $pck{$c_type[$_]};
587 0 0 0     0 croak "FATAL: your perl does not support 64bitint (avoid using type longlong)" if $c_pack[$_] eq 'q' && NO64BITINT;
588 0 0       0 croak "FATAL: invalid type '$c_type[$_]' for column $_" if !$c_pack[$_];
589 0         0 $c_sizeof[$_] = length pack($c_pack[$_], 1);
590 0 0       0 $c_pdl[$_] = $c_dt[$_] ? PDL::DateTime->new(zeroes(longlong, $allocated)) : zeroes($c_type[$_], $allocated);
591 0         0 $c_dataref[$_] = $c_pdl[$_]->get_dataref;
592 0         0 $c_bad[$_] = $c_pdl[$_]->badvalue;
593 0         0 $c_idx[$_] = 0;
594 0         0 my $big = PDL::Core::howbig($c_pdl[$_]->get_datatype);
595 0 0       0 croak "FATAL: column $_ mismatch (type=$c_type[$_], sizeof=$c_sizeof[$_], big=$big)" if $big != $c_sizeof[$_];
596             }
597              
598 0 0       0 return (\@c_type, \@c_pack, \@c_sizeof, \@c_pdl, \@c_bad, \@c_dataref, \@c_idx, (@c_dt > 0 ? \@c_dt : undef), $allocated, $cols);
599             }
600              
601             sub _init_2D {
602 2     2   4 my ($coli, $colcount, $O) = @_;
603              
604 2         2 my $cols;
605 2 50       6 if (!defined $coli) { # take all columns
606 2         3 $cols = $colcount;
607             }
608             else {
609 0         0 $cols = scalar @$coli;
610 0   0     0 ($_<0 || $_>$colcount) and croak "FATAL: invalid column '$_' (column count=$colcount)" for (@$coli);
      0        
611             }
612 2 50 33     17 croak "FATAL: invalid column count" unless $cols && $cols > 0 && $cols <= $colcount;
      33        
613              
614 2         5 my $c_type = $O->{type};
615 2         7 my $c_pack = $pck{$c_type};
616 2 50 50     27 croak "FATAL: your perl does not support 64bitint (avoid using type longlong)" if $c_pack eq 'q' && NO64BITINT;
617 2 50       3 croak "FATAL: invalid type '$c_type' for column $_" if !$c_pack;
618              
619 2         3 my $allocated = $O->{reshape_inc};
620 2         5 my $c_sizeof = length pack($c_pack, 1);
621 2         6 my $c_pdl = zeroes($c_type, $cols, $allocated);
622 2         3883 my $c_dataref = $c_pdl->get_dataref;
623 2         10 my $c_bad = $c_pdl->badvalue;
624              
625 2         72 my $big = PDL::Core::howbig($c_pdl->get_datatype);
626 2 50       14 croak "FATAL: column $_ size mismatch (type=$c_type, sizeof=$c_sizeof, big=$big)" if $big != $c_sizeof;
627              
628 2         7 return ($c_type, $c_pack, $c_sizeof, $c_pdl, $c_bad, $c_dataref, $allocated, $cols);
629             }
630              
631             sub _strptime {
632 0     0   0 my ($string, $format) = @_;
633 0         0 return eval { int POSIX::floor(Time::Piece->strptime($string, $format)->epoch * 1_000_000 + 0.5) };
  0         0  
634             }
635              
636             1;
637              
638             __END__