File Coverage

blib/lib/TOML/Tiny/Writer.pm
Criterion Covered Total %
statement 133 148 89.8
branch 35 52 67.3
condition 3 6 50.0
subroutine 15 15 100.0
pod 0 7 0.0
total 186 228 81.5


line stmt bran cond sub pod time code
1             $TOML::Tiny::Writer::VERSION = '0.15';
2             use strict;
3 285     285   1774 use warnings;
  285         528  
  285         8647  
4 285     285   1277 no warnings qw(experimental);
  285         3831  
  285         7217  
5 285     285   1189 use v5.18;
  285         505  
  285         10867  
6 285     285   3340  
  285         820  
7             use B qw(svref_2object SVf_IOK SVf_NOK);
8 285     285   1407 use Data::Dumper;
  285         463  
  285         16632  
9 285     285   1597 use TOML::Tiny::Grammar;
  285         541  
  285         12040  
10 285     285   1576 use TOML::Tiny::Util qw(is_strict_array);
  285         545  
  285         56771  
11 285     285   1738  
  285         502  
  285         286259  
12             my @KEYS;
13              
14             my $data = shift;
15             my $param = ref($_[1]) eq 'HASH' ? $_[1] : undef;
16 761     761 0 1060  
17 761 50       1542 for (ref $data) {
18             when ('HASH') {
19 761         1343 return to_toml_table($data, $param);
20 761         1366 }
21 277         1110  
22             when ('ARRAY') {
23             return to_toml_array($data, $param);
24 484         738 }
25 64         190  
26             when ('SCALAR') {
27             if ($$data eq '1') {
28 420         729 return 'true';
29 0 0       0 } elsif ($$data eq '0') {
    0          
30 0         0 return 'false';
31             } else {
32 0         0 return to_toml($$_, $param);
33             }
34 0         0 }
35              
36             when ('JSON::PP::Boolean') {
37             return $$data ? 'true' : 'false';
38 420         645 }
39 15 100       94  
40             when ('Types::Serializer::Boolean') {
41             return $data ? 'true' : 'false';
42 405         536 }
43 0 0       0  
44             when ('DateTime') {
45             return strftime_rfc3339($data);
46 405         516 }
47 0         0  
48             when ('Math::BigInt') {
49             return $data->bstr;
50 405         482 }
51 1         6  
52             when ('Math::BigFloat') {
53             if ($data->is_inf || $data->is_nan) {
54 404         501 return lc $data->bstr;
55 6 50 33     24 } else {
56 0         0 return $data->bstr;
57             }
58 6         109 }
59              
60             when ('') {
61             # Thanks to ikegami on Stack Overflow for the trick!
62 398         589 # https://stackoverflow.com/questions/12686335/how-to-tell-apart-numeric-scalars-and-string-scalars-in-perl/12693984#12693984
63             # note: this must come before any regex can flip this flag off
64             if (svref_2object(\$data)->FLAGS & (SVf_IOK | SVf_NOK)) {
65             return 'inf' if Math::BigFloat->new($data)->is_inf;
66 398 100       2837 return '-inf' if Math::BigFloat->new($data)->is_inf('-');
67 176 50       617 return 'nan' if Math::BigFloat->new($data)->is_nan;
68 176 50       35361 return $data;
69 176 50       16442 }
70 176         15206 #return $data if svref_2object(\$data)->FLAGS & (SVf_IOK | SVf_NOK);
71             return $data if $data =~ /$DateTime/;
72             return lc($data) if $data =~ /^$SpecialFloat$/;
73 222 100       1531  
74 198 100       2343 return to_toml_string($data);
75             }
76 189         465  
77             default{
78             die 'unhandled: '.Dumper($_);
79 0         0 }
80 0         0 }
81             }
82              
83             my ($data, $param) = @_;
84             my @buff;
85              
86 5     5 0 8 for my $key (keys %$data) {
87 5         7 my $value = $data->{$key};
88              
89 5         13 if (ref $value eq 'HASH') {
90 6         9 push @buff, $key . '=' . to_toml_inline_table($value);
91             } else {
92 6 50       14 push @buff, $key . '=' . to_toml($value);
93 0         0 }
94             }
95 6         21  
96             return '{' . join(', ', @buff) . '}';
97             }
98              
99 5         21 my ($data, $param) = @_;
100             my @buff_assign;
101             my @buff_tables;
102              
103 277     277 0 607 # Generate simple key/value pairs for scalar data
104 277         411 for my $k (grep{ ref($data->{$_}) !~ /HASH|ARRAY/ } sort keys %$data) {
105             my $key = to_toml_key($k);
106             my $val = to_toml($data->{$k}, $param);
107             push @buff_assign, "$key=$val";
108 277         928 }
  540         1912  
109 323         665  
110 323         953 # For arrays, generate an array of tables if all elements of the array are
111 323         1521 # hashes. For mixed arrays, generate an inline array.
112             ARRAY: for my $k (grep{ ref $data->{$_} eq 'ARRAY' } sort keys %$data) {
113             # Empty table
114             if (!@{$data->{$k}}) {
115             my $key = to_toml_key($k);
116 277         962 push @buff_assign, "$key=[]";
  540         1520  
117             next ARRAY;
118 57 50       96 }
  57         194  
119 0         0  
120 0         0 # Mixed array
121 0         0 if (grep{ ref $_ ne 'HASH' } @{$data->{$k}}) {
122             my $key = to_toml_key($k);
123             my $val = to_toml($data->{$k}, $param);
124             push @buff_assign, "$key=$val";
125 57 100       85 }
  121         289  
  57         299  
126 37         135 # Array of tables
127 37         132 else {
128 37         151 push @KEYS, $k;
129              
130             for (@{ $data->{$k} }) {
131             push @buff_tables, '', '[[' . join('.', map{ to_toml_key($_) } @KEYS) . ']]';
132 20         50 push @buff_tables, to_toml($_);
133             }
134 20         72  
  20         57  
135 37         68 pop @KEYS;
  45         74  
136 37         102 }
137             }
138              
139 20         41 # Sub-tables
140             for my $k (grep{ ref $data->{$_} eq 'HASH' } sort keys %$data) {
141             if (!keys(%{$data->{$k}})) {
142             # Empty table
143             my $key = to_toml_key($k);
144 277         797 push @buff_assign, "$key={}";
  540         1154  
145 160 100       290 } else {
  160         431  
146             # Generate [table]
147 24         43 push @KEYS, $k;
148 24         71 push @buff_tables, '', '[' . join('.', map{ to_toml_key($_) } @KEYS) . ']';
149             push @buff_tables, to_toml($data->{$k}, $param);
150             pop @KEYS;
151 136         248 }
152 136         223 }
  270         404  
153 136         400  
154 136         260 join "\n", @buff_assign, @buff_tables;
155             }
156              
157             my ($data, $param) = @_;
158 277         1238  
159             if (@$data && $param->{strict}) {
160             my ($ok, $err) = is_strict_array($data);
161             die "toml: found heterogenous array, but strict is set ($err)\n" unless $ok;
162 64     64 0 107 }
163              
164 64 50 66     279 my @items;
165 0         0  
166 0 0       0 for my $item (@$data) {
167             if (ref $item eq 'HASH') {
168             push @items, to_toml_inline_table($item, $param);
169 64         90 } else {
170             push @items, to_toml($item, $param);
171 64         136 }
172 123 100       219 }
173 5         11  
174             return "[\n" . join("\n", map{ " $_," } @items) . "\n]";
175 118         262 }
176              
177             my $str = shift;
178              
179 64         163 if ($str =~ /^$BareKey$/) {
  123         440  
180             return $str;
181             }
182              
183 699     699 0 1332 # Escape control characters
184             $str =~ s/([\p{General_Category=Control}])/'\\u00' . unpack('H2', $1)/eg;
185 699 100       5025  
186 672         12701 # Escape unicode characters
187             #$str =~ s/($NonASCII)/'\\u00' . unpack('H2', $1)/eg;
188              
189             if ($str =~ /^"/) {
190 27         863 return qq{'$str'};
  3         58  
191             } else {
192             return qq{"$str"};
193             }
194             }
195 27 100       80  
196 4         16 state $escape = {
197             "\n" => '\n',
198 23         96 "\r" => '\r',
199             "\t" => '\t',
200             "\f" => '\f',
201             "\b" => '\b',
202             "\"" => '\"',
203 189     189 0 669 "\\" => '\\\\',
204             "\'" => '\\\'',
205             };
206              
207             my ($arg) = @_;
208             $arg =~ s/(["\\\b\f\n\r\t])/$escape->{$1}/g;
209             $arg =~ s/([\p{General_Category=Control}])/'\\u00' . unpack('H2', $1)/eg;
210              
211             return '"' . $arg . '"';
212             }
213              
214 189         356 #-------------------------------------------------------------------------------
215 189         522 # Adapted from DateTime::Format::RFC3339.
216 189         292 #-------------------------------------------------------------------------------
  2         10  
217             my ($dt) = @_;
218 189         585 my $tz;
219              
220             #-----------------------------------------------------------------------------
221             # Calculate the time zone offset for non-UTC time zones.
222             #
223             # TOML uses RFC3339 for datetimes, but supports a "local datetime" which
224             # excludes the timezone offset. A DateTime with a floating time zone
225 6     6 0 25879 # indicates a TOML local datetime.
226 6         6 #
227             # DateTime::Format::RFC3339 requires a time zone, however, and defaults to
228             # +00:00 for floating time zones. To support local datetimes in output,
229             # format the datetime as RFC3339 and strip the timezone when encountering a
230             # floating time zone.
231             #-----------------------------------------------------------------------------
232             if ($dt->time_zone_short_name eq 'floating') {
233             $tz = '';
234             } elsif ($dt->time_zone->is_utc) {
235             $tz = 'Z';
236             } else {
237             my $sign = $dt->offset < 0 ? '-' : '+';
238             my $secs = abs $dt->offset;
239              
240 6 100       15 my $mins = int($secs / 60);
    100          
241 1         15 $secs %= 60;
242              
243 1         17 my $hours = int($mins / 60);
244             $mins %= 60;
245 4 100       305  
246 4         271 if ($secs) {
247             $dt = $dt->clone;
248 4         270 $dt->set_time_zone('UTC');
249 4         5 $tz = 'Z';
250             } else {
251 4         5 $tz = sprintf '%s%02d:%02d', $sign, $hours, $mins;
252 4         5 }
253             }
254 4 100       7  
255 1         3 my $format = $dt->nanosecond ? '%Y-%m-%dT%H:%M:%S.%9N' : '%Y-%m-%dT%H:%M:%S';
256 1         12 return $dt->strftime($format) . $tz;
257 1         156 }
258              
259 3         12 1;
260              
261              
262             =pod
263 6 50       15  
264 6         33 =encoding UTF-8
265              
266             =head1 NAME
267              
268             TOML::Tiny::Writer
269              
270             =head1 VERSION
271              
272             version 0.15
273              
274             =head1 AUTHOR
275              
276             Jeff Ober <sysread@fastmail.fm>
277              
278             =head1 COPYRIGHT AND LICENSE
279              
280             This software is copyright (c) 2021 by Jeff Ober.
281              
282             This is free software; you can redistribute it and/or modify it under
283             the same terms as the Perl 5 programming language system itself.
284              
285             =cut