File Coverage

blib/lib/Jasonify.pm
Criterion Covered Total %
statement 139 162 85.8
branch 89 140 63.5
condition 6 9 66.6
subroutine 47 59 79.6
pod 10 13 76.9
total 291 383 75.9


line stmt bran cond sub pod time code
1 2     2   13043 use v5.14;
  2         7  
2 2     2   10 use warnings;
  2         3  
  2         100  
3              
4             package Jasonify v0.20.060;
5             # ABSTRACT: Just Another Serialized Object Notation library.
6              
7              
8 2     2   14 use Carp (); #qw( carp );
  2         3  
  2         55  
9 2     2   1146 use Datify v0.20.060 ();
  2         33968  
  2         69  
10 2     2   18 use LooksLike v0.20.060 (); #qw( number representation );
  2         21  
  2         34  
11 2     2   10 use Scalar::Util (); #qw( blessed reftype );
  2         4  
  2         24  
12 2     2   10 use String::Tools (); #qw( subst );
  2         3  
  2         31  
13              
14 2     2   1039 use parent 'Datify';
  2         522  
  2         9  
15              
16              
17              
18              
19             ### Accessor ###
20              
21              
22              
23              
24             ### Setter ###
25              
26              
27              
28              
29             __PACKAGE__->set(
30             # Varify/Encode options
31             #name => '',
32             #assign => undef,
33             #list => undef,
34             list_sep => ', ',
35             beautify => undef,
36             );
37              
38              
39              
40             __PACKAGE__->set(
41             # Undefify options
42             null => 'null',
43             );
44              
45              
46              
47             __PACKAGE__->set(
48             # Booleanify options
49             false => 'false',
50             true => 'true',
51             );
52              
53              
54              
55             __PACKAGE__->set(
56             # Stringify options
57             quote => '"',
58             #quote1 => undef,
59             quote2 => '"',
60             #q1 => undef,
61             #q2 => undef,
62             #sigils => undef,
63             longstr => -1,
64             #encode1 => undef,
65             encode2 => {
66             map( { $_ => sprintf( '\\u%04x', $_ ) }
67             0x00 .. 0x1f, 0x7f, # Control characters (C0)
68             0x80 .. 0x9f, # Control characters (C1)
69             0x2028, 0x2029, # Characters not allowed by Javascript
70             ),
71             # Special cases
72             map( { ord( eval qq!"$_"! ) => $_ } qw( \b \t \n \r \" \\\\ ) ),
73             utf => 16,
74             byte => '\\u00%02x',
75             wide => '\\u%04x',
76             },
77             #qpairs => undef,
78             #qquotes => undef,
79             );
80              
81              
82              
83             __PACKAGE__->set(
84             # Numify options
85             infinite => '"Infinity"',
86             -infinite => '"-Infinity"',
87             nonnumber => '"NaN"',
88             #num_sep => undef,
89             );
90              
91              
92              
93             __PACKAGE__->set(
94             # Lvalueify options
95             lvalue => '$lvalue',
96             );
97              
98              
99              
100             __PACKAGE__->set(
101             # Vstringify options
102             vformat => '"\\u%0*v4x"',
103             vsep => '\\u',
104             );
105              
106              
107             #=option Regexpify options
108             #
109             #=over
110             #
111             #=item ...
112             #
113             #=back
114             #
115             #=cut
116             #
117             #__PACKAGE__->set(
118             # # Regexpify options
119             # #quote3 => undef,
120             # #q3 => undef,
121             # #encode3 => undef,
122             #);
123              
124              
125              
126             __PACKAGE__->set(
127             # Arrayify options
128             array_ref => '[$_]',
129             );
130              
131              
132              
133             __PACKAGE__->set(
134             # Hashify options
135             hash_ref => '{$_}',
136             pair => '$key : $value',
137             keysort => \&Datify::keysort,
138             keyfilter => undef,
139             keyfilterdefault => 1,
140             #keywords => undef,
141             );
142              
143              
144              
145             __PACKAGE__->set(
146             # Objectify options
147             json_method => 'TO_JSON',
148             object => '$data',
149             #object => '{$class_str : $data}',
150             overloads => [qw( "" 0+ )],
151             tag => undef,
152             #tag => '($class_str)$data',
153             tag_method => 'FREEZE',
154             );
155              
156              
157              
158             __PACKAGE__->set(
159             # Ioify options
160             io => 'null',
161             );
162              
163              
164              
165             __PACKAGE__->set(
166             # Codeify options
167             code => 'null',
168             #codename => undef,
169             #body => undef,
170             );
171              
172              
173              
174             __PACKAGE__->set(
175             # Refify options
176             reference => '$_',
177             dereference => '$referent$place',
178             #nested => undef,
179             );
180              
181              
182              
183             __PACKAGE__->set(
184             # Formatify options
185             format => 'null',
186             );
187              
188              
189              
190             # Override Datify::booleanify() for SCALAR refs
191             sub booleanify {
192 72     72 1 1516 my $self = &Datify::self;
193 72 50       481 local $_ = shift if @_;
194 72 100       152 return $self->undefify unless defined;
195 63 100       138 return $self->booleanify($$_) if 'SCALAR' eq ref;
196 59 100       187 return $_ ? $Jasonify::Boolean::true : $Jasonify::Boolean::false;
197             }
198              
199              
200              
201             # Override Datify::keyify() to appropriately stringify all keys
202             sub keyify {
203 138     138 1 24311 my $self = &Datify::self;
204 138 50       948 local $_ = shift if @_;
205              
206 138         346 return LooksLike::representation(
207             $_,
208              
209             $_ => $self->stringify($_),
210             "infinity" => $Jasonify::Number::inf,
211             "-infinity" => $Jasonify::Number::ninf,
212             "nan" => $Jasonify::Number::nan,
213             );
214             }
215              
216             sub _objectify_via {
217 4     4   22 my $self = shift;
218 4         8 my $object = shift;
219              
220 4 100       9 if ( my $method_name = shift ) {
221 2         30 return $object->can($method_name);
222             }
223 2         8 return;
224             }
225             sub _objectify_via_tag {
226 2     2   81 my $self = shift;
227 2         3 my $object = shift;
228              
229 2   33     5 my $tag_method = $self->get('tag') && $self->get('tag_method');
230 2         24 return $self->_objectify_via( $object => $tag_method );
231             }
232             sub _objectify_via_json {
233 2     2   5 my $self = shift;
234 2         3 my $object = shift;
235              
236 2         6 return $self->_objectify_via( $object => $self->get('json_method') );
237             }
238              
239              
240              
241             # Override Datify::objectify() to appropriately stringify objects
242             sub objectify {
243 55     55 1 93 my $self = &Datify::self;
244 55         244 my $object = shift;
245              
246 55 50       135 return $self->scalarify($object)
247             unless defined( my $class = Scalar::Util::blessed($object) );
248              
249 55         110 my $object_str = $self->get('object');
250              
251 55         441 my $data;
252 55 100       112 if (0) {
    50          
    50          
    50          
    50          
253 0         0 } elsif ( my $code = $self->_find_handler($class) ) {
254 53         1081 return $self->$code($object);
255             } elsif ( my $tag = $self->_objectify_via_tag($object) ) {
256 0         0 $object_str = $self->get('tag');
257 0         0 $data = $self->arrayify( $object->$tag('JSON') );
258             } elsif ( my $to_json = $self->_objectify_via_json($object) ) {
259 0         0 $data = $self->scalarify( $object->$to_json() );
260             } elsif ( my $method = $self->overloaded($object) ) {
261 0         0 $data = $self->scalarify( $object->$method() );
262             } elsif ( my $attrkeyvals = $object->can('_attrkeyvals') ) {
263             # TODO: Look this up via meta-objects and such.
264 0         0 $data = $self->hashify( $object->$attrkeyvals() );
265             } else {
266 2         166 $data = Scalar::Util::reftype $object;
267              
268 2 0       14 $data
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    50          
269             = $data eq 'ARRAY' ? $self->arrayify( @$object )
270             : $data eq 'CODE' ? $self->codeify( $object )
271             : $data eq 'FORMAT' ? $self->formatify( $object )
272             : $data eq 'GLOB' ? $self->globify( $object )
273             : $data eq 'HASH' ? $self->hashify( $object )
274             : $data eq 'IO' ? $self->ioify( $object )
275             : $data eq 'REF' ? $self->scalarify( $$object )
276             : $data eq 'REGEXP' ? $self->regexpify( $object )
277             : $data eq 'SCALAR' ? $self->scalarify( $$object )
278             : $self->undefify;
279             }
280              
281 2         207 return String::Tools::subst(
282             $object_str,
283             class_str => $self->stringify($class),
284             class => $class,
285             data => $data,
286             );
287             }
288              
289              
290              
291             # Override Datify::regexpify() to appropriately stringify regular expressions
292             sub regexpify {
293 2     2 1 4 my $self = &Datify::self;
294 2 50       13 local $_ = shift if @_;
295              
296 2         7 return $self->stringify($_);
297             }
298              
299             # Override Datify::varify so that it throws an error
300             sub varify;
301              
302              
303              
304             # Override Datify::vstringify so that it encodes a vstring as appropriate
305             sub vstringify {
306 2     2 1 6 my $self = &Datify::self;
307 2 50       14 local $_ = shift if @_;
308              
309             # Encode as a vstring if vformat has been specified
310             # or as a regular string if vformat has not been specified
311 2 50       6 return $self->get('vformat')
312             ? $self->SUPER::vstringify($_)
313             : $self->stringify($_);
314             }
315              
316              
317              
318             # Override Datify::scalarify to properly handle all of the various types
319             sub _scalarify {
320 347     347   94747 my $self = &Datify::self;
321 347 50       2145 local $_ = shift if @_;
322              
323 347 100       739 return $self->undefify unless defined $_;
324              
325 336 100       775 if ( defined( my $blessed = Scalar::Util::blessed($_) ) ) {
326             return
327 57 100       158 $blessed eq 'Regexp' ? $self->regexpify($_)
328             : $self->objectify($_);
329             }
330              
331 279         536 my $ref = Scalar::Util::reftype $_;
332 279 100       518 if ( not $ref ) {
333             # Handle GLOB, LVALUE, and VSTRING
334 164         304 my $ref2 = ref \$_;
335             return
336 164 100 66     793 $ref2 eq 'GLOB' ? $self->globify($_)
    100          
    50          
    50          
337             : $ref2 eq 'LVALUE' ? $self->lvalueify($_)
338             : $ref2 eq 'VSTRING' ? $self->vstringify($_)
339             : $ref2 eq 'SCALAR' && LooksLike::number($_)
340             ? $self->numify($_)
341             : $self->stringify($_)
342             ;
343             }
344              
345             return
346 115 50       673 $ref eq 'ARRAY' ? $self->arrayify(@$_)
    100          
    50          
    100          
    100          
    50          
    100          
    50          
    50          
    100          
    100          
347             : $ref eq 'CODE' ? $self->codeify($_)
348             : $ref eq 'FORMAT' ? $self->formatify($_)
349             : $ref eq 'GLOB' ? $self->globify($$_)
350             : $ref eq 'HASH' ? $self->hashify($_)
351             : $ref eq 'IO' ? $self->ioify($_)
352             : $ref eq 'LVALUE' ? $self->booleanify($$_)
353             : $ref eq 'REF' ? $self->refify($$_)
354             : $ref eq 'REGEXP' ? $self->regexpify($_) # ???
355             : $ref eq 'SCALAR' ? $self->booleanify($$_)
356             : $ref eq 'VSTRING' ? $self->booleanify($$_)
357             : $self->objectify($_) # ???
358             ;
359             }
360              
361              
362              
363             # TODO
364             sub decode;
365              
366              
367              
368             sub encode {
369 53     53 1 3684 my $self = &Datify::self;
370 53 50       754 return unless @_;
371              
372 53         105 my @return = map { $self->scalarify($_) } @_;
  53         136  
373              
374 51         2283 $self->_cache_reset();
375              
376 51 50       717 return @_ == 1 ? $return[0] : @return;
377             }
378              
379              
380              
381             sub boolean {
382 11     11 1 4603 &Datify::class;
383 11 100       160 return @_ ? Jasonify::Boolean::bool( $_[-1] ) : 'Jasonify::Boolean';
384             }
385             *bool = \&boolean;
386              
387              
388              
389             sub literal {
390 10     10 1 2557 &Datify::class;
391 10 100       146 return @_ ? Jasonify::Literal->new( $_[-1] ) : 'Jasonify::Literal';
392             }
393              
394              
395              
396             sub number {
397 13     13 1 2680 &Datify::class;
398 13         151 my $count = scalar @_;
399             return
400 13 100       45 $count >= 2 ? Jasonify::Number->formatted(@_)
    100          
401             : $count == 1 ? Jasonify::Number->number(shift)
402             : 'Jasonify::Number'
403             ;
404             }
405              
406              
407              
408             sub string {
409 5     5 1 1017 &Datify::class;
410 5 100       74 return @_ ? Jasonify::Literal->string( $_[-1] ) : 'Jasonify::Literal';
411             }
412              
413             ### Private Methods & Settings ###
414             ### Do not use these methods & settings outside of this package,
415             ### they are subject to change or disappear at any time.
416 3093     3093   208577 sub _settings() { \state %SETTINGS }
417              
418             __PACKAGE__->set(
419             _cache_hit => 1, # Sets the caching to use the final representation
420             # or die if that doesn't exist
421             );
422              
423              
424             package
425             Jasonify::Literal;
426              
427 2     2   3108 use LooksLike (); #qw( zero );
  2         4  
  2         53  
428              
429             use overload
430 2         20 'bool' => 'bool',
431             '""' => 'as_string',
432 2     2   11 ;
  2         18  
433              
434             our $null = bless \do { my $null = Jasonify->get('null') }, __PACKAGE__;
435             our $false = bless \do { my $false = Jasonify->get('false') }, __PACKAGE__;
436             our $true = bless \do { my $true = Jasonify->get('true') }, __PACKAGE__;
437              
438 13     13 0 20 sub Jasonify::jasonify_literalify { $_[1]->as_string }
439             # OR
440             #Jasonify->add_handler( sub { $_[1]->as_string } );
441              
442 0     0   0 sub null() { $null }
443 0     0   0 sub false() { $false }
444 0     0   0 sub true() { $true }
445              
446             sub new {
447 26     26   392 my $class = &Datify::class;
448 26         269 my $literal = shift;
449 26 50       51 return $null unless defined($literal);
450 26 50       58 return $false unless length( $literal);
451 26         85 return bless \$literal, $class;
452             }
453             sub string {
454 4     4   17 @_ = ( shift, Jasonify->stringify(@_) );
455 4         2126 goto &new;
456             }
457             #sub comment {
458             # $_[0]->new(
459             # "# " . join( "\n# ", map { split /\n/ } @_[ 1 .. $#_ ] ) . "\n" );
460             #}
461              
462 180     180   11870 sub as_string { ${ $_[0] } }
  180         698  
463             sub bool {
464 25     25   3182 my $literal = ${ $_[0] };
  25         42  
465             return
466 25   100     193 $literal ne $$null
467             && $literal ne $$false
468             && $literal ne '""'
469             && $literal ne '"0"'
470             && !LooksLike::zero($literal);
471             }
472              
473             package
474             Jasonify::Number;
475              
476 2     2   846 use LooksLike (); #qw( number numeric representation );
  2         4  
  2         71  
477              
478             use overload
479 2         17 '0+' => 'as_num',
480             'neg' => 'negate',
481              
482             '<=>' => 'comparen',
483             'cmp' => 'compares',
484 2     2   17 ;
  2         6  
485 2     2   232 use parent -norequire => 'Jasonify::Literal';
  2         9  
  2         8  
486              
487             our $nan = bless \do { my $nan = Jasonify->get('nonnumber') }, __PACKAGE__;
488             our $inf = bless \do { my $inf = Jasonify->get( 'infinite') }, __PACKAGE__;
489             our $ninf = bless \do { my $ninf = Jasonify->get('-infinite') }, __PACKAGE__;
490              
491 20     20 0 49 sub Jasonify::jasonify_numberify { $_[1]->as_string }
492             # OR
493             #Jasonify->add_handler( sub { $_[1]->as_string } );
494              
495 0     0   0 sub nan() { $nan }
496 0     0   0 sub inf() { $inf }
497 0     0   0 sub ninf() { $ninf }
498              
499             my $number_regex = do {
500             my $digit09 = '[0123456789]';
501             my $digit19 = '[123456789]';
502             my $integer = "(?:0|$digit19+$digit09*)";
503             my $decimal = "(?:\.$digit09+)";
504             qr/-?$integer$decimal?(?:[Ee][+-]?$integer)?/;
505             };
506              
507 0 0   0   0 sub comparen { ( $_[2] ? -1 : +1 ) * ( $_[0]->as_num <=> $_[1] ) }
508 0 0   0   0 sub compares { ( $_[2] ? -1 : +1 ) * ( ${ $_[0] } cmp $_[1] ) }
  0         0  
509 0     0   0 sub as_num { eval ${ $_[0] } }
  0         0  
510             sub negate {
511 0     0   0 my $num = ${ $_[0] };
  0         0  
512             return
513 0 0       0 $num eq $$nan ? $nan
    0          
    0          
514             : $num eq $$inf ? $ninf
515             : $num eq $$ninf ? $inf
516 0 0       0 : $_[0]->number( $num =~ s/\A(-?)/$1 ? '' : '-'/er )
517             ;
518             }
519             sub number {
520 13     13   36 my $class = &Datify::class;
521 13         150 my $num = shift;
522 13 50       31 Carp::croak( "Not a number ", $num )
523             unless ( LooksLike::number($num) );
524              
525             return
526 13 50       508 LooksLike::numeric($num)
    50          
527             ? $num =~ /\A$number_regex\z/
528             ? $class->new($num)
529             : Carp::croak( "Malformed number ", $num )
530             : LooksLike::representation($num);
531             }
532              
533 9     9   97 sub formatted { return shift()->number( sprintf( shift(), @_ ) ) }
534 0     0   0 sub integer { return shift()->formatted( '%d', shift() ) }
535 0     0   0 sub float { return shift()->formatted( '%f', shift() ) }
536              
537             package
538             Jasonify::Boolean;
539              
540 2     2   1196 use Scalar::Util (); #qw( blessed );
  2         4  
  2         86  
541              
542             use overload
543 2         31 'bool' => 'value',
544             '0+' => 'value',
545             '""' => 'as_string',
546              
547             '<=>' => 'compare',
548             'cmp' => 'compare',
549              
550             '!' => 'negate',
551 2     2   11 ;
  2         4  
552              
553             our $false = bless \do { my $false = 0 }, __PACKAGE__;
554             our $true = bless \do { my $true = 1 }, __PACKAGE__;
555              
556 20     20 0 46 sub Jasonify::jasonify_booleanify { $_[1]->as_string }
557             # OR
558             #Jasonify->add_handler( sub { $_[1]->as_string } );
559              
560 2     2   4 sub false() { $false }
561 2     2   8 sub true() { $true }
562              
563 4     4   5 sub value { ${ $_[0] } }
  4         17  
564             sub as_string {
565 93 100   93   4740 ${ $_[0] } ? $Jasonify::Literal::true : $Jasonify::Literal::false;
  93         315  
566             }
567              
568 44 50   44   6146 sub compare { ( $_[2] ? -1 : +1 ) * ( ${ $_[0] } <=> ${ bool( $_[1] ) } ) }
  44         79  
  44         97  
569              
570 2 100   2   1204 sub negate { bool($_[0]) ? $false : $true }
571              
572             sub bool($) {
573             is_bool( $_[0] )
574             ? $_[0]
575             : ref( $_[0] ) eq 'SCALAR'
576 56 100   56   98 ? ${ $_[0] } ? $true : $false
  21 100       189  
    100          
    100          
577             : $_[0] ? $true : $false
578             ;
579             }
580 56 100   56   534 sub is_bool($) { Scalar::Util::blessed( $_[0] ) && $_[0]->isa(__PACKAGE__) }
581              
582             1;
583              
584             __END__