File Coverage

blib/lib/Bio/Gonzales/Util/Cerial.pm
Criterion Covered Total %
statement 67 137 48.9
branch 6 34 17.6
condition 0 5 0.0
subroutine 22 34 64.7
pod 5 13 38.4
total 100 223 44.8


line stmt bran cond sub pod time code
1             package Bio::Gonzales::Util::Cerial;
2              
3 21     21   1009035 use warnings;
  21         115  
  21         728  
4 21     21   121 use strict;
  21         42  
  21         584  
5 21     21   103 use Carp;
  21         41  
  21         1194  
6              
7 21     21   366 use v5.11;
  21         68  
8              
9 21     21   129 use Exporter 'import';
  21         42  
  21         915  
10              
11 21     21   3371 use Bio::Gonzales::Util::File qw/open_on_demand/;
  21         37  
  21         1631  
12 21     21   8189 use Bio::Gonzales::Util qw/deep_value flatten/;
  21         59  
  21         1401  
13              
14 21     21   12480 use Encode qw/encode_utf8/;
  21         303690  
  21         1585  
15 21     21   11533 use Try::Tiny;
  21         46439  
  21         1225  
16 21     21   9942 use YAML::XS;
  21         58767  
  21         1164  
17 21     21   16744 use JSON::XS;
  21         145482  
  21         1365  
18 21     21   165 use Data::Dumper;
  21         44  
  21         1091  
19 21     21   240 use Storable qw/nstore_fd fd_retrieve/;
  21         43  
  21         3336  
20              
21             our $VERSION = '0.083'; # VERSION
22              
23             our %EXPORT_TAGS = (
24             'all' => [
25             qw(
26             ndjson_iterate ndjson_hash ndjson_slurp ndjson_spew
27             ndjson_freeze ndjson_thaw
28             ythaw yfreeze yslurp yspew
29             jthaw jfreeze jslurp jspew
30             stoslurp stospew
31             )
32             ],
33             std => [
34             qw(
35             ythaw yfreeze yslurp yspew
36             jthaw jfreeze jslurp jspew
37             stoslurp stospew
38             )
39             ]
40             );
41             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
42             our @EXPORT = ( @{ $EXPORT_TAGS{'std'} } );
43              
44             BEGIN {
45 21     21   106 *yfreeze = \&YAML::XS::Dump;
46 21         48 *ythaw = \&YAML::XS::Load;
47             #*jfreeze = \&JSON::XS::encode_json;
48 21         31659 *jthaw = \&JSON::XS::decode_json;
49             }
50              
51             our $JSON = JSON::XS->new->indent(1)->utf8->allow_nonref;
52              
53             sub jfreeze {
54 2     2 1 9 my $r;
55 2         11 my @d = @_;
56             try {
57 2     2   278 $r = $JSON->encode(@d);
58             }
59             catch {
60 0     0   0 confess Dumper \@d;
61 2         78 };
62              
63             }
64              
65             sub _spew {
66 5     5   47 my $dest = shift;
67 5         14 my $data = shift;
68              
69 5         32 my ( $fh, $was_open ) = open_on_demand( $dest, '>' );
70 5 100       47 binmode $fh, ':utf8' unless ( ref $fh eq 'IO::Zlib' );
71 5         36 local $/ = "\n";
72              
73 5         287 print $fh $data;
74 5 100       255 $fh->close unless $was_open;
75             }
76              
77             sub _slurp {
78 7     7   21 my $src = shift;
79 7         43 my ( $fh, $was_open ) = open_on_demand( $src, '<' );
80 7 50       65 binmode $fh, ':utf8' unless ( ref $fh eq 'IO::Zlib' );
81 7         40 local $/ = "\n";
82              
83 7         15 my $data = do { local $/; <$fh> };
  7         18  
  7         244  
84              
85 7 50       93 $fh->close unless $was_open;
86 7         2735 return $data;
87             }
88              
89 5     5 1 1295 sub yslurp { return ythaw( encode_utf8(_slurp(shift)) ) }
90 2     2 1 1400 sub jslurp { return jthaw( encode_utf8(_slurp(shift)) ) }
91 3     3 1 2464 sub yspew { my $file = shift; _spew( $file, yfreeze( $_[0] ) ) }
  3         5602  
92 2     2 1 1820 sub jspew { my $file = shift; _spew( $file, jfreeze( $_[0] ) ) }
  2         36  
93              
94             sub stospew {
95 0     0 0   my $dest = shift;
96 0           my $data = shift;
97              
98 0           my ( $fh, $was_open ) = open_on_demand( $dest, '>' );
99 0           nstore_fd( $data, $fh );
100 0 0         $fh->close unless ($was_open);
101             }
102              
103             sub stoslurp {
104 0     0 0   my $src = shift;
105 0           my ( $fh, $was_open ) = open_on_demand( $src, '<' );
106 0           my $data = fd_retrieve($fh);
107 0 0         $fh->close unless $was_open;
108 0           return $data;
109             }
110              
111             sub ndjson_freeze {
112 0     0 0   my $entries = shift;
113 0 0         return unless (@$entries);
114 0           state $js = JSON::XS->new->utf8->allow_nonref;
115 0           return join( "\n", ( map { $js->encode($_) } @$entries ) ) . "\n";
  0            
116             }
117              
118             sub ndjson_thaw {
119 0     0 0   my $data = shift;
120              
121 0 0         return unless ($data);
122 0           my @entries = split /\n/, $data;
123              
124 0 0         return unless (@entries);
125              
126 0           state $js = JSON::XS->new->utf8->allow_nonref;
127              
128 0           return [ map { $js->decode($_) } @entries ];
  0            
129             }
130              
131             sub ndjson_hash {
132 0     0 0   my $keys = shift;
133 0           my $files = shift;
134 0           my $cfg = shift;
135              
136 0           my %res;
137 0           my $it = ndjson_iterate($files);
138 0           while ( my $elem = $it->() ) {
139 0           my $val = deep_value( $elem, $keys );
140 0 0         if ( $cfg->{uniq} ) {
141 0 0         die $val . " already exists" if ( $res{$val} );
142 0           $res{$val} = $elem;
143             } else {
144 0   0       $res{$val} //= [];
145 0           push @{ $res{$val} }, $elem;
  0            
146             }
147             }
148 0           return \%res;
149             }
150              
151             sub ndjson_slurp {
152 0     0 0   my $it = ndjson_iterate(@_);
153              
154 0           my @res;
155 0           while ( defined( my $elem = $it->() ) ) {
156 0           push @res, $elem;
157             }
158 0           return \@res;
159             }
160              
161             sub ndjson_spew {
162 0     0 0   my ( $dest, $elems, $c ) = @_;
163              
164 0           my $js = JSON::XS->new->utf8->allow_nonref;
165 0 0         $js = $js->canonical(1) if ( $c->{canonical} );
166 0           my ( $fh, $fh_was_open ) = open_on_demand( $dest, '>' );
167              
168             try {
169 0     0     for my $elem (@$elems) {
170 0           say $fh $js->encode($elem);
171             }
172             }
173             catch {
174 0     0     confess "could not spew: $_";
175 0           };
176              
177 0 0         $fh->close unless ($fh_was_open);
178 0           return;
179             }
180              
181             sub ndjson_iterate {
182 0     0 0   my @srcs = flatten(@_);
183              
184 0           my $i = 0;
185 0           my ( $fh, $fh_was_open ) = open_on_demand( $srcs[$i], '<' );
186              
187             return sub {
188 0     0     while ( $i < @srcs ) {
189 0           while ( my $record = <$fh> ) {
190 0 0 0       next if ( !$record || $record =~ /^\s*$/ );
191 0           my $data;
192             try {
193 0           $data = decode_json($record);
194             }
195             catch {
196 0           warn "caught error: $_ JSON string >$record<";
197 0           };
198 0 0         confess "no valid data in record" unless ($data);
199              
200 0           return $data;
201             }
202              
203 0 0         $fh->close unless ($fh_was_open);
204 0 0         if ( ++$i >= @srcs ) {
205             # return if next file does not exist
206 0           return;
207             }
208              
209             # open next file
210 0           ( $fh, $fh_was_open ) = open_on_demand( $srcs[$i], '<' );
211             }
212 0           return;
213 0           };
214              
215             }
216              
217             __END__
218              
219             =head1 NAME
220              
221             Bio::Gonzales::Util::Cerial - convenience functions for yaml and json IO
222              
223             =head1 SYNOPSIS
224              
225             use Bio::Gonzales::Util::Cerial;
226              
227             # YAML IO
228             my $yaml_string = yfreeze(\%data);
229             my $data = ythaw($yaml_string);
230              
231             yspew($filename, \%data);
232             my $data = yslurp($filename);
233              
234             # JSON IO
235             my $json_string = jfreeze(\%data);
236             my $data = jthaw($json_string);
237              
238             jspew($filename, \%data);
239             my $data = jslurp($filename);
240              
241              
242             =head1 DESCRIPTION
243            
244             =item B<< $yaml_string = yfreeze(\%data) >>
245              
246             Serialize data structure as yaml string
247              
248             =item B<< $data = ythaw($yaml_string) >>
249              
250             UNserialize data structure from yaml string
251              
252             =item B<< yspew($filename, \%data) >>
253              
254             Serialize data structure as yaml string to a file
255              
256             =item B<< my $data = yslurp($filename) >>
257              
258             UNserialize data structure from yaml file
259              
260             =item B<< my $json_string = jfreeze(\%data) >>
261              
262             Serialize data structure as json string
263              
264             =item B<< my $data = jthaw($json_string) >>
265              
266             UNserialize data structure from json string
267              
268             =item B<< jspew($filename, \%data) >>
269              
270             Serialize data structure as json string to a file
271              
272             =item B<< my $data = jslurp($filename) >>
273              
274             UNserialize data structure from json file
275              
276             =head1 EXPORT
277              
278             The following functions are exported by default
279              
280             ythaw
281             yfreeze
282             yslurp
283             yspew
284              
285             jthaw
286             jfreeze
287             jslurp
288             jspew
289              
290             =cut