File Coverage

blib/lib/Data/Dump/JavaScript.pm
Criterion Covered Total %
statement 63 65 96.9
branch 23 30 76.6
condition 7 12 58.3
subroutine 16 16 100.0
pod 3 3 100.0
total 112 126 88.8


line stmt bran cond sub pod time code
1             package Data::Dump::JavaScript;
2             $Data::Dump::JavaScript::VERSION = '0.001';
3 1     1   22531 use strict;
  1         2  
  1         22  
4 1     1   4 use warnings;
  1         1  
  1         20  
5 1     1   2 use Exporter 'import';
  1         2  
  1         20  
6 1     1   2 use Scalar::Util 'blessed';
  1         1  
  1         73  
7 1     1   4 use Encode ();
  1         1  
  1         657  
8              
9             # ABSTRACT: Pretty printing of data structures as JavaScript
10              
11              
12             our @EXPORT_OK = qw( dump_javascript false true );
13              
14             # Literal names
15             # Users may override Booleans with literal 0 or 1 if desired.
16             our($FALSE, $TRUE) = map { bless \(my $dummy = $_), 'Data::Dump::JavaScript::_Bool' } 0, 1;
17              
18             # Escaped special character map with u2028 and u2029
19             my %ESCAPE = (
20             '"' => '"',
21             '\\' => '\\',
22             '/' => '/',
23             'b' => "\x08",
24             'f' => "\x0c",
25             'n' => "\x0a",
26             'r' => "\x0d",
27             't' => "\x09",
28             'u2028' => "\x{2028}",
29             'u2029' => "\x{2029}"
30             );
31             my %REVERSE = map { $ESCAPE{$_} => "\\$_" } keys %ESCAPE;
32              
33             for(0x00 .. 0x1f) {
34             my $packed = pack 'C', $_;
35             $REVERSE{$packed} = sprintf '\u%.4X', $_ unless defined $REVERSE{$packed};
36             }
37              
38             my $indent_level;
39             # standard and semi-standard JS default
40             my $indent_count = 2;
41              
42              
43 2     2 1 255 sub false () {$FALSE} ## no critic (prototypes)
44              
45              
46 3     3 1 770 sub true () {$TRUE} ## no critic (prototypes)
47              
48              
49             sub dump_javascript {
50 35     35 1 8340 $indent_level = 0;
51 35         56 Encode::encode 'UTF-8', _encode_value(shift);
52             }
53              
54             sub _encode_key {
55 7     7   6 my $str = shift;
56 7         10 $str =~ s!([\x00-\x1f\x{2028}\x{2029}\\"/])!$REVERSE{$1}!gs;
57 7         14 return "$str";
58             }
59              
60             sub _encode_array {
61 33     33   29 my $str = '[';
62             $str .= "\n"
63 33 100       22 if scalar @{$_[0]} > 1;
  33         60  
64 33         19 $indent_level++;
65 33 100       24 $str .= join(",\n", map { scalar @{$_[0]} > 1 ? _get_indented(_encode_value($_)) : _encode_value($_) } @{$_[0]});
  33         43  
  33         65  
  33         44  
66 33         28 $indent_level--;
67 33 100       20 $str .= scalar @{$_[0]} > 1
  33         102  
68             ? "\n" . _get_indented("]")
69             : ']';
70             }
71              
72             sub _encode_object {
73 8     8   22 my $object = shift;
74 8         8 my $str = '{';
75 8 100       18 $str .= "\n"
76             if keys %$object > 0;
77 8         8 $indent_level++;
78 8         21 my @pairs = map { _get_indented(_encode_key($_)) . ': ' . _encode_value($object->{$_}) }
  7         9  
79             sort keys %$object;
80             #$str .= join(",\n", @pairs) . "\n";
81 8         11 $str .= join(",\n", @pairs);
82 8         3 $indent_level--;
83 8 100       16 $str .= keys %$object > 0
84             ? "\n" . _get_indented("}")
85             : '}';
86 8         22 return $str;
87             }
88              
89             sub _encode_string {
90 15     15   12 my $str = shift;
91 15         68 $str =~ s!([\x00-\x1f\x{2028}\x{2029}\\"/])!$REVERSE{$1}!gs;
92 15         51 return "'$str'";
93             }
94              
95             sub _encode_value {
96 75     75   61 my $value = shift;
97              
98             # Reference
99 75 100       128 if (my $ref = ref $value) {
100              
101             # Object
102 46 100       70 return _encode_object($value) if $ref eq 'HASH';
103              
104             # Array
105 38 100       72 return _encode_array($value) if $ref eq 'ARRAY';
106              
107             # True or false
108 5 0       8 return $$value ? 'true' : 'false' if $ref eq 'SCALAR';
    50          
109 5 100       33 return $value ? 'true' : 'false' if $ref eq 'Data::Dump::JavaScript::_Bool';
    50          
110              
111             # Blessed reference with TO_JSON method
112 0 0 0     0 if (blessed $value && (my $sub = $value->can('TO_JSON'))) {
113 0         0 return _encode_value($value->$sub);
114             }
115             }
116              
117             # Null
118 29 100       46 return 'null' unless defined $value;
119              
120              
121             # Number (bitwise operators change behavior based on the internal value type)
122              
123             # "0" & $x will modify the flags on the "0" on perl < 5.14, so use a copy
124 26         20 my $zero = "0";
125             # "0" & $num -> 0. "0" & "" -> "". "0" & $string -> a character.
126             # this maintains the internal type but speeds up the xor below.
127 26         35 my $check = $zero & $value;
128 26 50 100     209 return $value
      66        
      66        
129             if length $check
130             # 0 ^ itself -> 0 (false)
131             # $character ^ itself -> "\0" (true)
132             && !($check ^ $check)
133             # filter out "upgraded" strings whose numeric form doesn't strictly match
134             && 0 + $value eq $value
135             # filter out inf and nan
136             && $value * 0 == 0;
137              
138             # String
139 15         19 return _encode_string($value);
140             }
141              
142             sub _get_indented {
143 35     35   88 return ' ' x ( $indent_level * $indent_count ) . shift;
144             }
145              
146             # Emulate boolean type
147             package Data::Dump::JavaScript::_Bool;
148             $Data::Dump::JavaScript::_Bool::VERSION = '0.001';
149 1     1   962 use overload '""' => sub { ${$_[0]} }, fallback => 1;
  1     10   1476  
  1         6  
  10         7  
  10         24  
150              
151              
152             1;
153              
154             __END__