File Coverage

blib/lib/Astro/QDP/Parse.pm
Criterion Covered Total %
statement 155 163 95.0
branch 29 38 76.3
condition 11 17 64.7
subroutine 22 22 100.0
pod 3 3 100.0
total 220 243 90.5


line stmt bran cond sub pod time code
1             # --8<--8<--8<--8<--
2             #
3             # Copyright (C) 2008 Smithsonian Astrophysical Observatory
4             #
5             # This file is part of Astro::QDP::Parse
6             #
7             # Astro::QDP::Parse is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 3 of the License, or (at
10             # your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20             # -->8-->8-->8-->8--
21              
22             package Astro::QDP::Parse;
23              
24 3     3   68688 use strict;
  3         9  
  3         131  
25 3     3   18 use warnings;
  3         7  
  3         86  
26 3     3   73 use 5.008;
  3         15  
  3         139  
27              
28 3     3   15 use Carp;
  3         6  
  3         356  
29              
30             our $VERSION = '0.13';
31              
32 3     3   3791 use Text::Abbrev;
  3         135  
  3         168  
33              
34 3     3   2359 use Clone qw( clone );
  3         22568  
  3         336  
35 3     3   3410 use IO::File;
  3         46350  
  3         464  
36 3     3   3984 use Regexp::Common qw{ number };
  3         18810  
  3         16  
37 3     3   11674 use List::Util qw{ first };
  3         6  
  3         383  
38 3     3   4077 use List::MoreUtils qw{ pairwise };
  3         4463  
  3         444  
39 3     3   4829 use Params::Validate qw{ :all };
  3         38004  
  3         8690  
40              
41             ## no critic (ProhibitAccessOfPrivateData)
42              
43              
44 3     3   1240 my $have_PDL = eval 'use PDL::Core qw( pdl ); 1;'; ## no critic
  0         0  
  0         0  
45              
46              
47             require Exporter;
48              
49             our @ISA = qw(Exporter);
50              
51             our %EXPORT_TAGS = ( 'all' => [ qw(
52             read_qdpfile
53             parse_qdp
54             parse_qdpfile
55             ) ] );
56              
57             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
58              
59             our @EXPORT = qw(
60             );
61              
62 24     24   226 sub _normalize_keys { return lc $_[0] };
63              
64             my %parse_qdp_spec = (
65             as_pdl => { type => BOOLEAN, default => 0 },
66             normalize => { type => BOOLEAN, default => 0},
67             );
68              
69             #========================================================================
70              
71              
72             sub parse_qdpfile
73             {
74 4     4 1 37527 my @pos = ( shift );
75 4         115 my ( $file ) = validate_pos( @pos, { type => SCALAR } );
76              
77 4         84 my %opt = validate_with( params => \@_,
78             spec => \%parse_qdp_spec,
79             normalize_keys => \&_normalize_keys
80             );
81              
82 4 50 33     42 croak( "piddle output requested, but PDL is not available\n" )
83             if $opt{as_pdl} && ! $have_PDL;
84              
85 4         15 my $lines = read_qdpfile( $file );
86              
87 4         23 return parse_qdp( $lines, \%opt );
88             }
89              
90             #-------------------------------------------------------------------
91             sub read_qdpfile
92             {
93 8     8 1 15145 my ( $file ) = @_;
94 8 50       86 my $fh = new IO::File $file
95             or croak( __PACKAGE__, "::read_qdpfile: unable to open $file\n" );
96              
97 8         1162 my @lines;
98              
99             my $line;
100 8         300 while ( defined( $line = $fh->getline ) ) {
101 1955         66395 chomp $line;
102 1955         3895 $line =~ s/^\s+//;
103 1955         5244 $line =~ s/\s+$//;
104              
105 1955 100       4276 if ( $line =~ /-$/ ) {
106 82         134 chop $line;
107 82         1691 chomp( my $l1 = $fh->getline );
108 82         2208 $l1 =~ s/^\s+//;
109 82         184 $l1 =~ s/\s+$//;
110 82         170 $line .= " $l1";
111 82 100       238 redo unless $fh->eof;
112             }
113              
114 1875         58632 push @lines, $line;
115             }
116 8         318 $fh->close;
117 8         318 return \@lines;
118             }
119              
120             #-------------------------------------------------------------------
121             sub parse_qdp
122             {
123 4     4 1 12 my @pos = ( shift );
124 4         120 my ( $lines ) = validate_pos( @pos, { type => ARRAYREF } );
125              
126 4         94 my %opt = validate_with( params => \@_,
127             spec => \%parse_qdp_spec,
128             normalize_keys => \&_normalize_keys
129             );
130              
131 4         35 my $hdr = _parse_qdp_hdr( $lines );
132              
133 4         15 return _parse_qdp_datagroups( $hdr, $lines, \%opt );
134             }
135              
136             #-------------------------------------------------------------------
137             sub _parse_qdp_hdr
138             {
139 4     4   7 my ( $lines ) = @_;
140              
141 4         32 my %hdr = ( serr => [],
142             terr => [],
143             skip => 0,
144             plt => [],
145             ); # header info
146              
147             # serr and terr must be at the beginning of the qdp file.
148 4         64 while( $lines->[0] =~ /^\s*read\s+(s|t)(?:err)?\s+(.*)/i )
149             {
150 5         12 chomp( my $line = shift @$lines);
151              
152 5         60 $hdr{lc $1 . 'err'} = [ split(' ', $2) ];
153             }
154              
155             {
156             # now find first line of data so can figure out vectors
157 4     12   5 my $dline = first { /^$RE{num}{real}/ } @$lines;
  4         83  
  12         1484  
158              
159 4 50       497 croak( "no data in qdp file?\n" )
160             if ! defined $dline;
161              
162 4         9 chomp $dline;
163 4         240 my @data = split(' ', $dline);
164 4         15 $hdr{ncols} = @data;
165             }
166              
167             #------------------
168             # determine number of vectors. a vector consists of a data column plus
169             # 0, 1, or 2 error columns.
170 4         9 my $nvec = $hdr{ncols} - @{$hdr{serr}} - 2 * @{$hdr{terr}};
  4         12  
  4         13  
171              
172             # initialize list of vectors
173 4         15 my @vec = map { { errtype => 0 } } 1..$nvec;
  13         33  
174              
175             # set non-default error types
176 4         11 $vec[$_-1]{errtype} = 1 foreach @{$hdr{serr}};
  4         23  
177 4         7 $vec[$_-1]{errtype} = 2 foreach @{$hdr{terr}};
  4         16  
178              
179             # flush out vectors, creating indices to data file columns
180             # for each vector component (data and error column(s))
181 4         7 my $idx = 0;
182 4         8 my $hdg = 0;
183 4         8 for my $vec ( @vec )
184             {
185 13         23 $vec->{hdg} = $hdg++;
186 13         21 $vec->{start} = $idx;
187 13         54 $idx += $vec->{errtype} + 1;
188 13         21 $vec->{data} = [];
189 13 100       42 if ( $vec->{errtype} == 1 )
    100          
190             {
191 7         21 $vec->{err} = [];
192             }
193             elsif ( $vec->{errtype} == 2 )
194             {
195 1         3 $vec->{elo} = [];
196 1         5 $vec->{ehi} = [];
197             }
198             }
199              
200 4         9 $hdr{vecs} = \@vec;
201              
202 4         12 return \%hdr;
203             }
204              
205             #-------------------------------------------------------------------
206             sub _parse_qdp_datagroups
207             {
208 4     4   8 my ( $hdr, $lines, $opts ) = @_;
209              
210 4         5 my @groups;
211              
212 4         6 my $vdg = 0;
213 4         4 my $dg = 1;
214 4         13 while ( @$lines )
215             {
216 12         30 my ( $x, @y) = _parse_qdp_datagroup( $hdr, $lines, $opts );
217              
218 12         26 $x->{vdg} = $vdg;
219 12         22 for my $y ( @y )
220             {
221 22         39 $y->{vdg} = $vdg;
222 22         44 $y->{dg} = $dg++;
223 22         251 push @groups, { x => $x, y => $y };
224             }
225 12         40 $vdg++;
226             }
227              
228 4         40 delete $hdr->{vecs};
229 4         8 delete $hdr->{skip};
230              
231 4         44 return \@groups, $hdr;
232             }
233              
234              
235             #-------------------------------------------------------------------
236             sub _parse_qdp_datagroup
237             {
238 12     12   18 my ( $hdr, $lines, $opt ) = @_;
239              
240             # make copy of vector templates, as the templates
241             # are reused for "vertical" data groups.
242 12         453 my $vecs = clone $hdr->{vecs};
243              
244             # create a list of arrayrefs, in the same order as the input data tokens,
245             # to speed up processing of data
246 12 100       33 my @drefs = map { $_->{errtype} == 0 ? ( $_->{data} )
  34 100       143  
247             : $_->{errtype} == 1 ? ( $_->{data}, $_->{err} )
248             : ( $_->{data}, $_->{elo}, $_->{ehi} )
249             }
250             @$vecs;
251              
252 12         35 _parse_horiz_datagroup( $hdr, $lines, @drefs );
253              
254 12 50       43 if ( $opt->{as_pdl} )
255             {
256 0         0 for my $vec ( @$vecs )
257             {
258 0         0 $vec->{$_} = pdl( $vec->{$_} )
259 0         0 foreach grep { exists $vec->{$_} }
260             qw ( data err elo ehi );
261             }
262             }
263              
264 12 50       30 if ( $opt->{normalize} )
265             {
266 0         0 $_->{elo} = $_->{ehi} = delete $_->{err}
267 0         0 foreach grep { exists $_->{err} } @$vecs;
268             }
269              
270 12         68 return @$vecs;
271             }
272              
273             #-------------------------------------------------------------------
274             sub _parse_horiz_datagroup {
275              
276 12     12   24 my ( $hdr, $lines, @cols ) = @_;
277              
278 12         19 my $nskip = 0;
279 12         25 while( @$lines )
280             {
281 1734         2940 my $line = shift @$lines;
282 1734         2624 chomp $line;
283              
284 1734 100 100     12386 if ( $hdr->{skip} && $line =~ /^\s*NO\s+/ )
285             {
286             # $NO is the number of *additional* NO lines
287 8         14 my $NO = 0;
288 8   66     63 $NO++ while $NO < @$lines && $lines->[$NO] =~ /^\s*NO\s+/;
289              
290 8 50 33     49 if ( $hdr->{skip} && $hdr->{skip} <= $NO+1 )
291             {
292 8         18 splice(@$lines, 0, $NO);
293 8         28 return;
294             }
295             }
296              
297              
298 1726 100 66     13338 if ( $line =~ /^\s*$RE{num}{real}/ || $line =~ /^\s*NO\s+/ )
299             {
300 1718 50       269924 my @data = map { $_ eq 'NO' ? undef : $_ } split( ' ', $line );
  8716         30945  
301 1718 50       8397 if ( @data != @cols )
302             {
303 0         0 croak( 'unexpected number of data points: ',
304             'got ', scalar @data,
305             ' expected ', scalar @cols,
306             "\n" );
307             }
308              
309 1718         5134 push @{$_}, shift @data foreach @cols;
  8716         41301  
310             }
311              
312             else
313             {
314 8         1117 _parse_plt_command( $hdr, $line );
315             }
316             }
317              
318 4         14 return;
319             }
320              
321             #-------------------------------------------------------------------
322              
323             my %PLT = abbrev qw( skip off single double );
324              
325             sub _parse_plt_command {
326              
327 77     77   104 my ( $hdr, $line ) = @_;
328              
329              
330 77         82 push @{ $hdr->{plt} }, $line;
  77         180  
331              
332              
333             # need to process some .pco commands (e.g. skip) while reading
334             # in data; if it's an indirection ("@filename") recursively handle that
335              
336 77 100       168 if ( $line =~ /^\s*\@(.*)/ )
337             {
338 3         10 my $lines = read_qdpfile($1);
339              
340             # don't push the expanded commands in the saved list of plt commands
341 3         10 my $plts = $hdr->{plt};
342 3         10 $hdr->{plt} = [];
343 3         22 _parse_plt_command( $hdr, $_ ) foreach @$lines;
344 3         14 $hdr->{plt} = $plts;
345             }
346              
347             else
348             {
349 74         201 my ( $cmd, @opts ) = split( ' ', $line );
350              
351 74   100     259 $cmd = $PLT{lc $cmd} || '';
352              
353 74 100       168 if ( $cmd eq 'skip' )
354             {
355 4         9 my $opt = $PLT{lc $opts[0]};
356 4 50       10 croak( "unrecognized argument to PLT skip command: $opts[0]\n" )
357             unless defined $opt;
358              
359 4         34 $hdr->{skip} = { off => 0,
360             single => 1,
361             double => 2,
362             }->{$opt};
363             }
364             }
365              
366 77         245 return;
367             }
368              
369             1;
370              
371              
372             __END__