File Coverage

blib/lib/App/NDTools/Slurp.pm
Criterion Covered Total %
statement 74 120 61.6
branch 17 58 29.3
condition 4 12 33.3
subroutine 16 18 88.8
pod 0 7 0.0
total 111 215 51.6


line stmt bran cond sub pod time code
1             package App::NDTools::Slurp;
2              
3             # input/output related subroutines for NDTools
4              
5 7     7   46 use strict;
  7         30  
  7         212  
6 7     7   34 use warnings FATAL => 'all';
  7         23  
  7         299  
7 7     7   53 use parent qw(Exporter);
  7         13  
  7         112  
8 7     7   3857 use open qw(:std :utf8);
  7         9215  
  7         45  
9              
10 7     7   1147 use File::Basename qw(basename);
  7         17  
  7         643  
11 7     7   4766 use JSON qw();
  7         76109  
  7         201  
12 7     7   47 use Scalar::Util qw(readonly);
  7         12  
  7         332  
13 7     7   3064 use YAML::XS qw();
  7         17513  
  7         164  
14              
15 7     7   77 use App::NDTools::INC;
  7         16  
  7         41  
16 7     7   3465 use Log::Log4Cli;
  7         84355  
  7         10402  
17              
18             our @EXPORT_OK = qw(
19             s_decode
20             s_dump
21             s_dump_file
22             s_encode
23             s_fmt_by_uri
24             s_load
25             s_load_uri
26             );
27              
28             our %FORMATS = (
29             JSON => {
30             allow_nonref => 1,
31             canonical => 1,
32             pretty => 1,
33             relaxed => 1,
34             },
35             );
36              
37             # YAML::XS decode boolean values as PL_sv_yes and PL_sv_no, both - read only
38             # at leas until https://github.com/ingydotnet/yaml-libyaml-pm/issues/25
39             sub _fix_decoded_yaml_bools($) {
40 0     0   0 my @stack = (\$_[0]);
41 0         0 my $ref;
42              
43 0         0 while ($ref = shift @stack) {
44 0 0       0 if (ref ${$ref} eq 'ARRAY') {
  0 0       0  
45 0         0 for (reverse 0 .. $#{${$ref}}) {
  0         0  
  0         0  
46 0 0       0 if (ref ${$ref}->[$_]) {
  0 0       0  
47 0         0 push @stack, \${$ref}->[$_];
  0         0  
48 0         0 } elsif (readonly ${$ref}->[$_]) {
49 0 0       0 splice @{${$ref}}, $_, 1, (${$ref}->[$_] ? JSON::true : JSON::false);
  0         0  
  0         0  
  0         0  
50             }
51             }
52 0         0 } elsif (ref ${$ref} eq 'HASH') {
53 0         0 for (keys %{${$ref}}) {
  0         0  
  0         0  
54 0 0       0 if (ref ${$ref}->{$_}) {
  0 0       0  
55 0         0 push @stack, \${$ref}->{$_};
  0         0  
56 0         0 } elsif (readonly ${$ref}->{$_}) {
57 0 0       0 ${$ref}->{$_} = delete ${$ref}->{$_} ? JSON::true : JSON::false;
  0         0  
  0         0  
58             }
59             }
60             }
61             }
62             }
63              
64             sub s_decode($$;$) {
65 8     8 0 27 my ($data, $fmt, $opts) = @_;
66 8         21 my $format = uc($fmt);
67              
68 8 50       24 if ($format eq 'JSON') {
    0          
    0          
69 8 50       17 $data = eval { JSON::from_json($data, {%{$FORMATS{JSON}}, %{$opts || {}}}) };
  8         23  
  8         31  
  8         68  
70             } elsif ($format eq 'YAML') {
71 0         0 $data = eval { YAML::XS::Load($data) };
  0         0  
72 0 0       0 die_fatal "Failed to decode '$fmt': " . $@, 4 if $@;
73 0         0 _fix_decoded_yaml_bools($data);
74             } elsif ($format eq 'RAW') {
75             ;
76             } else {
77 0         0 die_fatal "Unable to decode '$fmt' (not supported)";
78             }
79 8 50       421 die_fatal "Failed to decode '$fmt': " . $@, 4 if $@;
80              
81 8         50 return $data;
82             }
83              
84             sub s_dump(@) {
85 1     1 0 3 my ($uri, $fmt, $opts) = (shift, shift, shift);
86 1 50       4 $uri = \*STDOUT if ($uri eq '-');
87              
88 1 50       26 $fmt = s_fmt_by_uri($uri) unless (defined $fmt);
89 1         3 my $data = join('', map { s_encode($_, $fmt, $opts) } @_);
  1         2  
90 1 50       4 if (ref $uri eq 'GLOB') {
91 1         60 print $uri $data;
92             } else {
93 0         0 s_dump_file($uri, $data);
94             }
95             }
96              
97             sub s_dump_file($$) {
98 0     0 0 0 my ($file, $data) = @_;
99              
100 0 0       0 open(my $fh, '>', $file) or die_fatal "Failed to open '$file' ($!)", 2;
101 0         0 print $fh $data;
102 0         0 close($fh);
103             }
104              
105             sub s_encode($$;$) {
106 1     1 0 3 my ($data, $fmt, $opts) = @_;
107 1         2 my $format = uc($fmt);
108              
109 1 50 0     29 if ($format eq 'JSON' or $format eq 'RAW' and ref $data) {
    0 33        
    0          
110 1 50       10 $data = eval { JSON::to_json($data, {%{$FORMATS{JSON}}, %{$opts || {}}}) };
  1         3  
  1         3  
  1         9  
111             } elsif ($format eq 'YAML') {
112 0         0 $data = eval { YAML::XS::Dump($data) };
  0         0  
113             } elsif ($format eq 'RAW') {
114 0         0 $data .= "\n";
115             } else {
116 0         0 die_fatal "Unable to encode to '$fmt' (not supported)";
117             }
118 1 50       49 die_fatal "Failed to encode structure to $fmt: " . $@, 4 if $@;
119              
120 1         5 return $data;
121             }
122              
123             sub s_fmt_by_uri($) {
124 9     9 0 415 my @names = split(/\./, basename(shift));
125 9 100 66     80 if (@names and @names > 1) {
126 8         31 my $ext = uc(pop @names);
127 8 50 33     46 return 'YAML' if ($ext eq 'YML' or $ext eq 'YAML');
128             }
129 9         25 return 'JSON'; # by default
130             }
131              
132             sub s_load($$;@) {
133 9     9 0 22 my ($uri, $fmt, %opts) = @_;
134 9 50       23 $uri = \*STDIN if ($uri eq '-');
135              
136 9         21 my $data = s_load_uri($uri);
137 8 50       59 $fmt = s_fmt_by_uri($uri) unless (defined $fmt);
138 8         29 return s_decode($data, $fmt);
139             }
140              
141             sub s_load_uri($) {
142 9     9 0 18 my $uri = shift;
143 9         12 my $data;
144              
145 9 50       38 if (ref $uri eq 'GLOB') {
146 0         0 $data = do { local $/; <$uri> };
  0         0  
  0         0  
147             } else {
148 9 100       425 open(my $fh, '<', $uri) or die_fatal "Failed to open file '$uri' ($!)", 2;
149 8         30 $data = do { local $/; <$fh> }; # load whole file
  8         36  
  8         225  
150 8         99 close($fh);
151             }
152              
153 8         30 return $data;
154             }
155              
156             1;