File Coverage

blib/lib/Jasonify.pm
Criterion Covered Total %
statement 168 194 86.6
branch 102 162 62.9
condition 11 15 73.3
subroutine 55 70 78.5
pod 12 16 75.0
total 348 457 76.1


line stmt bran cond sub pod time code
1 2     2   14411 use v5.14;
  2         7  
2 2     2   12 use warnings;
  2         4  
  2         96  
3              
4             package Jasonify v0.20.064;
5             # ABSTRACT: Just Another Serialized Object Notation library.
6              
7              
8 2     2   15 use Carp (); #qw( carp );
  2         3  
  2         59  
9 2     2   1198 use Datify v0.20.064 ();
  2         36441  
  2         68  
10 2     2   12 use LooksLike v0.20.060 (); #qw( number representation );
  2         24  
  2         37  
11 2     2   8 use Scalar::Util (); #qw( blessed reftype );
  2         4  
  2         26  
12 2     2   9 use String::Tools (); #qw( subst );
  2         5  
  2         34  
13              
14 2     2   857 use parent 'Datify';
  2         582  
  2         11  
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             keymap => \&Jasonify::keymap,
138             keysort => \&Datify::keysort,
139             keyfilter => undef,
140             keyfilterdefault => 1,
141             #keywords => undef,
142             );
143              
144              
145              
146             __PACKAGE__->set(
147             # Objectify options
148             json_method => 'TO_JSON',
149             object => '$data',
150             #object => '{$class_str : $data}',
151             overloads => [qw( "" 0+ )],
152             tag => undef,
153             #tag => '($class_str)$data',
154             tag_method => 'FREEZE',
155             );
156              
157              
158              
159             __PACKAGE__->set(
160             # Ioify options
161             io => 'null',
162             );
163              
164              
165              
166             __PACKAGE__->set(
167             # Codeify options
168             code => 'null',
169             #codename => undef,
170             #body => undef,
171             );
172              
173              
174              
175             __PACKAGE__->set(
176             # Refify options
177             reference => '$_',
178             dereference => '$referent$place',
179             #nested => undef,
180             );
181              
182              
183              
184             __PACKAGE__->set(
185             # Formatify options
186             format => 'null',
187             );
188              
189              
190              
191             # Override Datify::booleanify() for SCALAR refs
192             sub booleanify {
193 72     72 1 1497 my $self = &Datify::self;
194 72 50       496 local $_ = shift if @_;
195 72 100       132 return $self->undefify unless defined;
196 63 100       121 return $self->booleanify($$_) if 'SCALAR' eq ref;
197 59 100       181 return $_ ? $Jasonify::Boolean::true : $Jasonify::Boolean::false;
198             }
199              
200              
201              
202             # Override Datify::keyify() to appropriately stringify all keys
203             sub keyify {
204 139     139 1 23833 my $self = &Datify::self;
205 139 50       935 local $_ = shift if @_;
206              
207 139         252 my $blessed = Scalar::Util::blessed($_);
208 139 100 66     485 return defined($blessed) && $blessed->isa("Jasonify::Literal")
209             ? $_
210             : $self->stringify($_);
211             }
212              
213             # Override Datify::hashkeyvals to handle Jasonify::_key elements
214             sub hashkeyvals {
215 13     13 0 327 my $self = shift;
216 13         17 my $hash = shift;
217              
218             return map {
219 13         33 my $blessed = Scalar::Util::blessed($_);
  142         41999  
220 142 100       215 if ( defined($blessed) ) {
221 8 50       26 Carp::croak("Cannot handle $blessed")
222             unless $blessed->isa("Jasonify::_key");
223 8         16 ( $_->string() => $hash->{ $_->key() } );
224             } else {
225 134         258 ( $_ => $hash->{$_} );
226             }
227             } $self->hashkeys($hash);
228             }
229              
230             # Implement a keymap for unusual numbers
231             sub keymap {
232 142     142 1 3468 my $self = &Datify::self;
233 142 50       798 local $_ = shift if @_;
234              
235 142 100 100     238 return $_ unless ( LooksLike::infinity($_) || LooksLike::nan($_) );
236              
237 8         175 my $rep = LooksLike::representation(
238             $_,
239              
240             "infinity" => [ $Jasonify::Number::inf, Jasonify->get("infinite") ],
241             "-infinity" => [ $Jasonify::Number::ninf, Jasonify->get("-infinite") ],
242             "nan" => [ $Jasonify::Number::nan, Jasonify->get("nonnumber") ],
243             );
244             # key string sortby
245             # ======= ============== ===========
246             # "inf", '"Infinity"', "Infinity"
247             # "-inf", '"-Infinity"', "-Infinity"
248             # "nan", '"NaN"', "NaN"
249 8         447 return Jasonify::_key->new( $_, @$rep );
250             }
251              
252             sub _objectify_via {
253 4     4   21 my $self = shift;
254 4         5 my $object = shift;
255              
256 4 100       11 if ( my $method_name = shift ) {
257 2         23 return $object->can($method_name);
258             }
259 2         7 return;
260             }
261             sub _objectify_via_tag {
262 2     2   76 my $self = shift;
263 2         4 my $object = shift;
264              
265 2   33     4 my $tag_method = $self->get('tag') && $self->get('tag_method');
266 2         28 return $self->_objectify_via( $object => $tag_method );
267             }
268             sub _objectify_via_json {
269 2     2   4 my $self = shift;
270 2         3 my $object = shift;
271              
272 2         6 return $self->_objectify_via( $object => $self->get('json_method') );
273             }
274              
275              
276              
277             # Override Datify::objectify() to appropriately stringify objects
278             sub objectify {
279 55     55 1 100 my $self = &Datify::self;
280 55         273 my $object = shift;
281              
282 55 50       144 return $self->scalarify($object)
283             unless defined( my $class = Scalar::Util::blessed($object) );
284              
285 55         132 my $object_str = $self->get('object');
286              
287 55         492 my $data;
288 55 100       120 if (0) {
    50          
    50          
    50          
    50          
289 0         0 } elsif ( my $code = $self->_find_handler($class) ) {
290 53         1125 return $self->$code($object);
291             } elsif ( my $tag = $self->_objectify_via_tag($object) ) {
292 0         0 $object_str = $self->get('tag');
293 0         0 $data = $self->arrayify( $object->$tag('JSON') );
294             } elsif ( my $to_json = $self->_objectify_via_json($object) ) {
295 0         0 $data = $self->scalarify( $object->$to_json() );
296             } elsif ( my $method = $self->overloaded($object) ) {
297 0         0 $data = $self->scalarify( $object->$method() );
298             } elsif ( my $attrkeyvals = $object->can('_attrkeyvals') ) {
299             # TODO: Look this up via meta-objects and such.
300 0         0 $data = $self->hashify( $object->$attrkeyvals() );
301             } else {
302 2         150 $data = Scalar::Util::reftype $object;
303              
304 2 0       17 $data
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    50          
305             = $data eq 'ARRAY' ? $self->arrayify( @$object )
306             : $data eq 'CODE' ? $self->codeify( $object )
307             : $data eq 'FORMAT' ? $self->formatify( $object )
308             : $data eq 'GLOB' ? $self->globify( $object )
309             : $data eq 'HASH' ? $self->hashify( $object )
310             : $data eq 'IO' ? $self->ioify( $object )
311             : $data eq 'REF' ? $self->scalarify( $$object )
312             : $data eq 'REGEXP' ? $self->regexpify( $object )
313             : $data eq 'SCALAR' ? $self->scalarify( $$object )
314             : $self->undefify;
315             }
316              
317 2         191 return String::Tools::subst(
318             $object_str,
319             class_str => $self->stringify($class),
320             class => $class,
321             data => $data,
322             );
323             }
324              
325              
326              
327             # Override Datify::regexpify() to appropriately stringify regular expressions
328             sub regexpify {
329 2     2 1 6 my $self = &Datify::self;
330 2 50       14 local $_ = shift if @_;
331              
332 2         6 return $self->stringify($_);
333             }
334              
335             # Override Datify::varify so that it throws an error
336             sub varify;
337              
338              
339              
340             # Override Datify::vstringify so that it encodes a vstring as appropriate
341             sub vstringify {
342 2     2 1 6 my $self = &Datify::self;
343 2 50       14 local $_ = shift if @_;
344              
345             # Encode as a vstring if vformat has been specified
346             # or as a regular string if vformat has not been specified
347 2 50       6 return $self->get('vformat')
348             ? $self->SUPER::vstringify($_)
349             : $self->stringify($_);
350             }
351              
352              
353             sub numify {
354 58     58 1 775 my $self = &Datify::self;
355 58 50       336 local $_ = shift if @_;
356              
357 58 50       105 return $self->undefify unless defined;
358              
359             return
360 58 50       118 $self->is_numeric($_) ? $_
    100          
361             : LooksLike::number($_) ? LooksLike::representation(
362             $_,
363              
364             "infinity" => $Jasonify::Number::inf,
365             "-infinity" => $Jasonify::Number::ninf,
366             "nan" => $Jasonify::Number::nan,
367             )
368             : $Jasonify::Number::nan;
369             }
370              
371              
372             # Override Datify::scalarify to properly handle all of the various types
373             sub _scalarify {
374 348     348   91788 my $self = &Datify::self;
375 348 50       2206 local $_ = shift if @_;
376              
377 348 100       682 return $self->undefify unless defined $_;
378              
379 337 100       803 if ( defined( my $blessed = Scalar::Util::blessed($_) ) ) {
380             return
381 57 100       166 $blessed eq 'Regexp' ? $self->regexpify($_)
382             : $self->objectify($_);
383             }
384              
385 280         494 my $ref = Scalar::Util::reftype $_;
386 280 100       514 if ( not $ref ) {
387             # Handle GLOB, LVALUE, and VSTRING
388 164         312 my $ref2 = ref \$_;
389             return
390 164 100 66     739 $ref2 eq 'GLOB' ? $self->globify($_)
    100          
    50          
    50          
391             : $ref2 eq 'LVALUE' ? $self->lvalueify($_)
392             : $ref2 eq 'VSTRING' ? $self->vstringify($_)
393             : $ref2 eq 'SCALAR' && LooksLike::number($_)
394             ? $self->numify($_)
395             : $self->stringify($_)
396             ;
397             }
398              
399             return
400 116 50       600 $ref eq 'ARRAY' ? $self->arrayify(@$_)
    100          
    50          
    100          
    100          
    50          
    100          
    50          
    50          
    100          
    100          
401             : $ref eq 'CODE' ? $self->codeify($_)
402             : $ref eq 'FORMAT' ? $self->formatify($_)
403             : $ref eq 'GLOB' ? $self->globify($$_)
404             : $ref eq 'HASH' ? $self->hashify($_)
405             : $ref eq 'IO' ? $self->ioify($_)
406             : $ref eq 'LVALUE' ? $self->booleanify($$_)
407             : $ref eq 'REF' ? $self->refify($$_)
408             : $ref eq 'REGEXP' ? $self->regexpify($_) # ???
409             : $ref eq 'SCALAR' ? $self->booleanify($$_)
410             : $ref eq 'VSTRING' ? $self->booleanify($$_)
411             : $self->objectify($_) # ???
412             ;
413             }
414              
415              
416              
417             # TODO
418             sub decode;
419              
420              
421              
422             sub encode {
423 53     53 1 3291 my $self = &Datify::self;
424 53 50       711 return unless @_;
425              
426 53         119 my @return = map { $self->scalarify($_) } @_;
  53         141  
427              
428 51         2429 $self->_cache_reset();
429              
430 51 50       705 return @_ == 1 ? $return[0] : @return;
431             }
432              
433              
434              
435             sub boolean {
436 11     11 1 5590 &Datify::class;
437 11 100       188 return @_ ? Jasonify::Boolean::bool( $_[-1] ) : 'Jasonify::Boolean';
438             }
439             *bool = \&boolean;
440              
441              
442              
443             sub literal {
444 10     10 1 3056 &Datify::class;
445 10 100       188 return @_ ? Jasonify::Literal->new( $_[-1] ) : 'Jasonify::Literal';
446             }
447              
448              
449              
450             sub number {
451 13     13 1 3276 &Datify::class;
452 13         182 my $count = scalar @_;
453             return
454 13 100       52 $count >= 2 ? Jasonify::Number->formatted(@_)
    100          
455             : $count == 1 ? Jasonify::Number->number(shift)
456             : 'Jasonify::Number'
457             ;
458             }
459              
460              
461              
462             sub string {
463 5     5 1 1202 &Datify::class;
464 5 100       86 return @_ ? Jasonify::Literal->string( $_[-1] ) : 'Jasonify::Literal';
465             }
466              
467             ### Private Methods & Settings ###
468             ### Do not use these methods & settings outside of this package,
469             ### they are subject to change or disappear at any time.
470 3015     3015   157760 sub _settings() { \state %SETTINGS }
471              
472             __PACKAGE__->set(
473             _cache_hit => 1, # Sets the caching to use the final representation
474             # or die if that doesn't exist
475             );
476              
477              
478              
479             package
480             Jasonify::_key;
481              
482 2     2   4191 use parent -norequire => 'Jasonify::Literal';
  2         11  
  2         11  
483              
484             use overload
485 2         10 '""' => 'string',
486             'cmp' => 'compares',
487             '<=>' => 'comparen',
488 2     2   134 ;
  2         5  
489              
490             sub new {
491 8     8   29 my $class = &Datify::class;
492 8         134 my @self = @_; # key, string, sortby
493 8         33 return bless( \@self, $class );
494             }
495              
496 8     8   29 sub key { $_[0][ 0] }
497 42     42   6840 sub string { $_[0][+1] }
498 0     0   0 sub sortby { $_[0][-1] }
499              
500 0 0   0   0 sub comparen { ( $_[2] ? -1 : +1 ) * ( $_[0]->sortby <=> $_[1] ) }
501 0 0   0   0 sub compares { ( $_[2] ? -1 : +1 ) * ( $_[0]->sortby cmp $_[1] ) }
502              
503              
504             package
505             Jasonify::Literal;
506              
507 2     2   618 use LooksLike (); #qw( zero );
  2         4  
  2         62  
508              
509             use overload
510 2         16 'bool' => 'bool',
511             '""' => 'as_string',
512 2     2   11 ;
  2         5  
513              
514             our $null = bless \do { my $null = Jasonify->get('null') }, __PACKAGE__;
515             our $false = bless \do { my $false = Jasonify->get('false') }, __PACKAGE__;
516             our $true = bless \do { my $true = Jasonify->get('true') }, __PACKAGE__;
517              
518 13     13 0 25 sub Jasonify::jasonify_literalify { $_[1]->as_string }
519             # OR
520             #Jasonify->add_handler( sub { $_[1]->as_string } );
521              
522 0     0   0 sub null() { $null }
523 0     0   0 sub false() { $false }
524 0     0   0 sub true() { $true }
525              
526             sub new {
527 26     26   454 my $class = &Datify::class;
528 26         326 my $literal = shift;
529 26 50       65 return $null unless defined($literal);
530 26 50       54 return $false unless length( $literal);
531 26         131 return bless \$literal, $class;
532             }
533             sub string {
534 4     4   12 @_ = ( shift, Jasonify->stringify(@_) );
535 4         2605 goto &new;
536             }
537             #sub comment {
538             # $_[0]->new(
539             # "# " . join( "\n# ", map { split /\n/ } @_[ 1 .. $#_ ] ) . "\n" );
540             #}
541              
542 208     208   8950 sub as_string { ${ $_[0] } }
  208         818  
543             sub bool {
544 25     25   3918 my $literal = ${ $_[0] };
  25         49  
545             return
546 25   100     214 $literal ne $$null
547             && $literal ne $$false
548             && $literal ne '""'
549             && $literal ne '"0"'
550             && !LooksLike::zero($literal);
551             }
552              
553              
554             package
555             Jasonify::Number;
556              
557 2     2   862 use LooksLike (); #qw( number numeric representation );
  2         10  
  2         67  
558              
559             use overload
560 2         11 '0+' => 'as_num',
561             'neg' => 'negate',
562              
563             '<=>' => 'comparen',
564             'cmp' => 'compares',
565 2     2   11 ;
  2         11  
566 2     2   279 use parent -norequire => 'Jasonify::Literal';
  2         4  
  2         24  
567              
568             our ( $nan, $inf, $ninf )
569             = map {
570             bless \do { Jasonify->stringify($_) }, __PACKAGE__
571             } Jasonify->get(qw( nonnumber infinite -infinite ));
572              
573 20     20 0 41 sub Jasonify::jasonify_numberify { $_[1]->as_string }
574             # OR
575             #Jasonify->add_handler( sub { $_[1]->as_string } );
576              
577 0     0   0 sub nan() { $nan }
578 0     0   0 sub inf() { $inf }
579 0     0   0 sub ninf() { $ninf }
580              
581             my $number_regex = do {
582             my $digit09 = '[0123456789]';
583             my $digit19 = '[123456789]';
584             my $integer = "(?:0|$digit19+$digit09*)";
585             my $decimal = "(?:\.$digit09+)";
586             qr/-?$integer$decimal?(?:[Ee][+-]?$integer)?/;
587             };
588              
589 0 0   0   0 sub comparen { ( $_[2] ? -1 : +1 ) * ( $_[0]->as_num <=> $_[1] ) }
590 0 0   0   0 sub compares { ( $_[2] ? -1 : +1 ) * ( ${ $_[0] } cmp $_[1] ) }
  0         0  
591 0     0   0 sub as_num { eval ${ $_[0] } }
  0         0  
592             sub negate {
593 0     0   0 my $num = ${ $_[0] };
  0         0  
594             return
595 0 0       0 $num eq $$nan ? $nan
    0          
    0          
596             : $num eq $$inf ? $ninf
597             : $num eq $$ninf ? $inf
598 0 0       0 : $_[0]->number( $num =~ s/\A(-?)/$1 ? '' : '-'/er )
599             ;
600             }
601             sub number {
602 13     13   28 my $class = &Datify::class;
603 13         166 my $num = shift;
604 13 50       31 Carp::croak( "Not a number ", $num )
605             unless ( LooksLike::number($num) );
606              
607             return
608 13 50       342 LooksLike::numeric($num)
    50          
609             ? $num =~ /\A$number_regex\z/
610             ? $class->new($num)
611             : Carp::croak( "Malformed number ", $num )
612             : LooksLike::representation($num);
613             }
614              
615 9     9   103 sub formatted { return shift()->number( sprintf( shift(), @_ ) ) }
616 0     0   0 sub integer { return shift()->formatted( '%d', shift() ) }
617 0     0   0 sub float { return shift()->formatted( '%f', shift() ) }
618              
619              
620             package
621             Jasonify::Boolean;
622              
623 2     2   1272 use Scalar::Util (); #qw( blessed );
  2         11  
  2         85  
624              
625             use overload
626 2         8 'bool' => 'value',
627             '0+' => 'value',
628             '""' => 'as_string',
629              
630             '<=>' => 'compare',
631             'cmp' => 'compare',
632              
633             '!' => 'negate',
634 2     2   12 ;
  2         4  
635              
636             our $false = bless \do { my $false = 0 }, __PACKAGE__;
637             our $true = bless \do { my $true = 1 }, __PACKAGE__;
638              
639 20     20 0 46 sub Jasonify::jasonify_booleanify { $_[1]->as_string }
640             # OR
641             #Jasonify->add_handler( sub { $_[1]->as_string } );
642              
643 2     2   6 sub false() { $false }
644 2     2   7 sub true() { $true }
645              
646 4     4   6 sub value { ${ $_[0] } }
  4         17  
647             sub as_string {
648 93 100   93   4449 ${ $_[0] } ? $Jasonify::Literal::true : $Jasonify::Literal::false;
  93         316  
649             }
650              
651 44 50   44   6035 sub compare { ( $_[2] ? -1 : +1 ) * ( ${ $_[0] } <=> ${ bool( $_[1] ) } ) }
  44         83  
  44         94  
652              
653 2 100   2   1191 sub negate { bool($_[0]) ? $false : $true }
654              
655             sub bool($) {
656             is_bool( $_[0] )
657             ? $_[0]
658             : ref( $_[0] ) eq 'SCALAR'
659 56 100   56   88 ? ${ $_[0] } ? $true : $false
  21 100       190  
    100          
    100          
660             : $_[0] ? $true : $false
661             ;
662             }
663 56 100   56   536 sub is_bool($) { Scalar::Util::blessed( $_[0] ) && $_[0]->isa(__PACKAGE__) }
664              
665             1;
666              
667             __END__