File Coverage

blib/lib/JavaScript/Packer.pm
Criterion Covered Total %
statement 336 357 94.1
branch 100 126 79.3
condition 29 33 87.8
subroutine 25 25 100.0
pod 1 3 33.3
total 491 544 90.2


line stmt bran cond sub pod time code
1             package JavaScript::Packer;
2              
3 4     4   167549 use 5.008009;
  4         29  
4 4     4   18 use warnings;
  4         7  
  4         111  
5 4     4   19 use strict;
  4         6  
  4         78  
6 4     4   17 use Carp;
  4         18  
  4         205  
7 4     4   1518 use Regexp::RegGrp;
  4         13004  
  4         4166  
8              
9             # =========================================================================== #
10              
11             our $VERSION = "2.09";
12              
13             our @BOOLEAN_ACCESSORS = ( 'no_compress_comment', 'remove_copyright' );
14              
15             our @COPYRIGHT_ACCESSORS = ( 'copyright', 'copyright_comment' );
16              
17             our @COMPRESS_OPTS = ( 'clean', 'obfuscate', 'shrink', 'best' );
18             our $DEFAULT_COMPRESS = 'clean';
19              
20             our $PACKER_COMMENT = '\/\*\s*JavaScript::Packer\s*(\w+)\s*\*\/';
21             our $COPYRIGHT_COMMENT = '\/\*((?>[^\*]|\*[^\/])*copyright(?>[^\*]|\*[^\/])*)\*\/';
22              
23             our $RESTORE_PATTERN = qr~\x01(\d+)\x01~;
24             our $RESTORE_REPLACEMENT = "\x01%d\x01";
25              
26             our $SHRINK_VARS = {
27             BLOCK => qr/(((catch|do|if|while|with|function)\b[^~{};]*(\(\s*[^{};]*\s*\))\s*)?(\{[^{}]*\}))/, # function ( arg ) { ... }
28             ENCODED_BLOCK => qr/~#?(\d+)~/,
29             CALLER => qr/((?>[a-zA-Z0-9_\x24\.]+)\s*\([^\(\)]*\))(?=[,\)])/, # do_something( arg1, arg2 ) as argument of another function call
30             BRACKETS => qr/\{[^{}]*\}|\[[^\[\]]*\]|\([^\(\)]*\)|~[^~]+~/,
31             IDENTIFIER => qr~[a-zA-Z_\x24][a-zA-Z_0-9\\x24]*~,
32             SCOPED => qr/~#(\d+)~/,
33             VARS => qr~\b(?:var|function)\s+((?>[a-zA-Z0-9_\x24]+))~, # var x, funktion blah
34             PREFIX => qr~\x02~,
35             SHRUNK => qr~\x02\d+\b~
36             };
37              
38             our $BASE62_VARS = {
39             WORDS => qr~(\b[0-9a-zA-Z]\b|(?>[a-zA-Z0-9_]{2,}))~,
40             ENCODE10 => 'String',
41             ENCODE36 => 'function(c){return c.toString(36)}',
42             ENCODE62 => q~function(c){return(c<62?'':e(parseInt(c/62)))+((c=c%62)>35?String.fromCharCode(c+29):c.toString(36))}~,
43             UNPACK =>
44             q~eval(function(p,a,c,k,e,r){e=%s;if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'%s'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('%s',%s,%d,'%s'.split('|'),0,{}))~
45             };
46              
47             our $DICTIONARY = {
48             STRING1 => qr~"(?>(?:(?>[^"\\]+)|\\.|\\")*)"~,
49             STRING2 => qr~'(?>(?:(?>[^'\\]+)|\\.|\\')*)'~,
50             REGEXP => qr~\/(\\[\/\\]|[^*\/])(\\.|[^\/\n\\])*\/[gim]*~,
51             OPERATOR => qr'return|typeof|[\[(\^=,{}:;&|!*?]',
52             CONDITIONAL => qr~\/\*\@\w*|\w*\@\*\/|\/\/\@\w*|\@(?>\w+)~,
53              
54             # single line comments
55             COMMENT1 => qr~(:?)\/\/([\@#])?([^\n]*)?\n~,
56              
57             # multline comments
58             COMMENT2 => qr~\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/~
59             };
60              
61             our $DATA = [
62             { regexp => $DICTIONARY->{STRING1} },
63             { regexp => $DICTIONARY->{STRING2} },
64             { regexp => $DICTIONARY->{CONDITIONAL} },
65             {
66             regexp => '(' . $DICTIONARY->{OPERATOR} . ')\s*(' . $DICTIONARY->{REGEXP} . ')',
67             replacement => sub {
68             return sprintf( "%s%s", $_[0]->{submatches}->[0], $_[0]->{submatches}->[1] );
69             },
70             }
71             ];
72              
73             our $COMMENTS = [
74             {
75             regexp => ';;;[^\n]*\n',
76             replacement => ''
77             },
78             { regexp => $DICTIONARY->{COMMENT1} . '\s*(' . $DICTIONARY->{REGEXP} . ')?', },
79             { regexp => '(' . $DICTIONARY->{COMMENT2} . ')\s*(' . $DICTIONARY->{REGEXP} . ')?' }
80             ];
81              
82             our $CLEAN = [
83             {
84             regexp => '\(\s*([^;)]*)\s*;\s*([^;)]*)\s*;\s*([^;)]*)\)',
85             replacement => sub { return sprintf( "(%s;%s;%s)", @{ $_[0]->{submatches} } ); }
86             },
87             { regexp => 'throw[^};]+[};]' },
88             {
89             regexp => ';+\s*([};])',
90             replacement => sub { return $_[0]->{submatches}->[0]; }
91             }
92             ];
93              
94             our $WHITESPACE = [
95             { regexp => '\/\/@[^\n]*\n' },
96             {
97             regexp => '@\s+\b',
98             replacement => '@ '
99             },
100             {
101             regexp => '\b\s+@',
102             replacement => ' @'
103             },
104             {
105             regexp => '(\d)\s+(\.\s*[a-z\x24_\[(])',
106             replacement => sub { return sprintf( "%s %s", @{ $_[0]->{submatches} } ); }
107             },
108             {
109             regexp => '([+-])\s+([+-])',
110             replacement => sub { return sprintf( "%s %s", @{ $_[0]->{submatches} } ); }
111             },
112             {
113             regexp => '(?>\s+)(\x24)(?>\s+)',
114             replacement => sub { return sprintf( " %s ", $_[0]->{submatches}->[0] ); }
115             },
116             {
117             regexp => '(\x24)(?>\s+)(?!=)',
118             replacement => sub { return sprintf( "%s ", $_[0]->{submatches}->[0] ); }
119             },
120             {
121             regexp => '(?\s+)(\x24)',
122             replacement => sub { return sprintf( " %s", $_[0]->{submatches}->[0] ); }
123             },
124             {
125             regexp => '\b\s+\b',
126             replacement => ' '
127             },
128             {
129             regexp => '\s+',
130             replacement => ''
131             }
132             ];
133              
134             our $TRIM = [
135             {
136             regexp => '(\d)(?:\|\d)+\|(\d)',
137             replacement => sub { return sprintf( "%d-%d", $_[0]->{submatches}->[0] || 0, $_[0]->{submatches}->[1] || 0 ); }
138             },
139             {
140             regexp => '([a-z])(?:\|[a-z])+\|([a-z])',
141             replacement => sub { return sprintf( "%s-%s", $_[0]->{submatches}->[0], $_[0]->{submatches}->[1] ); }
142             },
143             {
144             regexp => '([A-Z])(?:\|[A-Z])+\|([A-Z])',
145             replacement => sub { return sprintf( "%s-%s", $_[0]->{submatches}->[0], $_[0]->{submatches}->[1] ); }
146             },
147             {
148             regexp => '\|',
149             replacement => ''
150             }
151             ];
152              
153             our @REGGRPS = ( 'comments', 'clean', 'whitespace', 'concat', 'trim', 'data_store', 'concat_store' );
154              
155             # --------------------------------------------------------------------------- #
156              
157             {
158 4     4   29 no strict 'refs';
  4         7  
  4         14872  
159              
160             foreach my $field ( @BOOLEAN_ACCESSORS ) {
161             next if defined *{ __PACKAGE__ . '::' . $field }{CODE};
162              
163             *{ __PACKAGE__ . '::' . $field } = sub {
164 99     99   175 my ( $self, $value ) = @_;
165              
166 99 100       199 $self->{ '_' . $field } = $value ? 1 : undef if ( defined( $value ) );
    100          
167              
168 99         296 return $self->{ '_' . $field };
169             };
170             }
171              
172             foreach my $field ( @COPYRIGHT_ACCESSORS ) {
173             $field = '_' . $field if ( $field eq 'copyright_comment' );
174             next if defined *{ __PACKAGE__ . '::' . $field }{CODE};
175              
176             *{ __PACKAGE__ . '::' . $field } = sub {
177 126     126   689 my ( $self, $value ) = @_;
178              
179 126 100 66     305 if ( defined( $value ) and not ref( $value ) ) {
180 46         348 $value =~ s/^\s*|\s*$//gs;
181 46         139 $self->{ '_' . $field } = $value;
182             }
183              
184 126         161 my $ret = '';
185              
186 126 100       272 if ( $self->{ '_' . $field } ) {
187 14         34 $ret = '/* ' . $self->{ '_' . $field } . ' */' . "\n";
188             }
189              
190 126         254 return $ret;
191             };
192             }
193              
194             foreach my $reggrp ( @REGGRPS ) {
195             next if defined *{ __PACKAGE__ . '::reggrp_' . $reggrp }{CODE};
196              
197             *{ __PACKAGE__ . '::reggrp_' . $reggrp } = sub {
198 413     413   641 my ( $self ) = shift;
199              
200 413         2078 return $self->{ '_reggrp_' . $reggrp };
201             };
202             }
203             }
204              
205             sub compress {
206 163     163 1 1744 my ( $self, $value ) = @_;
207              
208 163 100       293 if ( defined( $value ) ) {
209 29 100       101 if ( grep( $value eq $_, @COMPRESS_OPTS ) ) {
    50          
210 21         54 $self->{_compress} = $value;
211             }
212             elsif ( !$value ) {
213 0         0 $self->{_compress} = undef;
214             }
215             }
216              
217 163   66     368 $self->{_compress} ||= $DEFAULT_COMPRESS;
218              
219 163         428 return $self->{_compress};
220             }
221              
222             # these variables are used in the closures defined in the init function
223             # below - we have to use globals as using $self within the closures leads
224             # to a reference cycle and thus memory leak, and we can't scope them to
225             # the init method as they may change. they are set by the minify sub
226             our $reggrp_comments;
227             our $reggrp_clean;
228             our $reggrp_whitespace;
229              
230             sub init {
231 31     31 0 1309928 my $class = shift;
232 31         59 my $self = {};
233              
234 31         54 bless( $self, $class );
235              
236 31         59 @{ $self->{clean}->{reggrp_data} } = ( @$DATA, @$CLEAN );
  31         133  
237 31         80 @{ $self->{whitespace}->{reggrp_data} } = ( @$DATA[ 0, 1, 3 ], @$WHITESPACE );
  31         81  
238 31         69 $self->{trim}->{reggrp_data} = $TRIM;
239              
240 31         79 @{ $self->{data_store}->{reggrp_data} } = map {
241 31         65 {
242             regexp => $_->{regexp},
243 776     776   8712 store => sub { return sprintf( "%s", $_[0]->{match} ); },
244             replacement => sub {
245 776     776   64546 return sprintf( $RESTORE_REPLACEMENT, $_[0]->{store_index} );
246             },
247             }
248 124         532 } @$DATA;
249              
250             $self->{data_store}->{reggrp_data}->[-1]->{replacement} = sub {
251 9     9   1840 return sprintf( "%s$RESTORE_REPLACEMENT", $_[0]->{submatches}->[0], $_[0]->{store_index} );
252 31         156 };
253              
254             $self->{data_store}->{reggrp_data}->[-1]->{store} = sub {
255 9     9   108 return $_[0]->{submatches}->[1];
256 31         93 };
257              
258 31         76 @{ $self->{concat_store}->{reggrp_data} } = map {
259 31         65 my $data = $_;
  62         89  
260             {
261             regexp => $data->{regexp},
262             store => sub {
263 3193     3193   39306 my ( $quote, $string ) = $_[0]->{match} =~ /^(['"])(.*)(['"])$/;
264              
265 3193         6037 return $string;
266             },
267             replacement => sub {
268 3193     3193   185479 my ( $quote, $string ) = $_[0]->{match} =~ /^(['"])(.*)(['"])$/;
269              
270 3193         11499 return sprintf( "%s$RESTORE_REPLACEMENT%s", $quote, $_[0]->{store_index}, $quote );
271             },
272              
273 62         255 };
274             } @$DATA[ 0, 1 ];
275              
276 31         88 @{ $self->{concat}->{reggrp_data} } = map {
277 31         57 my $quote = $_;
  62         83  
278 62         68 my $restore_pattern = $RESTORE_PATTERN;
279 62         254 $restore_pattern =~ s/\(\\d\+\)/\\d\+/g;
280              
281 62         154 my $regexp = '(' . $quote . $restore_pattern . $quote . ')((?:\+' . $quote . $restore_pattern . $quote . ')+)';
282 62         1028 $regexp = qr/$regexp/;
283              
284             {
285             regexp => $regexp,
286             replacement => sub {
287 16     16   9145 my $submatches = $_[0]->{submatches};
288 16         21 my $ret = $submatches->[0];
289              
290 16         33 my $next_str = '^\+(' . $quote . $restore_pattern . $quote . ')';
291              
292 16         109 while ( my ( $next ) = $submatches->[1] =~ /$next_str/ ) {
293 20         37 chop( $ret );
294 20         35 $ret .= substr( $next, 1 );
295 20         114 $submatches->[1] =~ s/$next_str//;
296             }
297              
298 16         38 return $ret;
299             },
300 62         323 };
301             } ( '"', '\'' );
302              
303 31         64 @{ $self->{comments}->{reggrp_data} } = ( @$DATA[ 0, 1, 3 ], @$COMMENTS );
  31         70  
304              
305             # single line comment
306             $self->{comments}->{reggrp_data}->[-2]->{replacement} = sub {
307 1837     1837   410371 my $submatches = $_[0]->{submatches};
308              
309 1837 100       4543 if ( $submatches->[0] eq ':' ) {
    100          
    100          
310 2         10 return sprintf( "://%s", $submatches->[2] );
311             }
312             elsif ( $submatches->[1] eq '#' ) {
313 6         9 my $cmnt = sprintf( "%s//%s%s__NEW_LINE__", @{$submatches}[0 .. 2] );
  6         28  
314 6         18 return $cmnt;
315             }
316             elsif ( $submatches->[1] eq '@' ) {
317 4         12 $reggrp_comments->exec( \$submatches->[2] );
318 4         87 $reggrp_clean->exec( \$submatches->[2] );
319 4         728 $reggrp_whitespace->exec( \$submatches->[2] );
320              
321 4         762 return sprintf( "%s//%s%s\n%s", @{$submatches}[0 .. 3] );
  4         18  
322             }
323 1825         5123 return sprintf( "\n%s", $submatches->[3] );
324 31         160 };
325              
326             # multi line comments
327             $self->{comments}->{reggrp_data}->[-1]->{replacement} = sub {
328 178     178   81807 my $submatches = $_[0]->{submatches};
329 178 100       544 if ( $submatches->[0] =~ /^\/\*\@(.*)\@\*\/$/sm ) {
330 7         15 my $cmnt = $1;
331              
332 7         18 $reggrp_comments->exec( \$cmnt );
333 7         188 $reggrp_clean->exec( \$cmnt );
334 7         1101 $reggrp_whitespace->exec( \$cmnt );
335              
336 7         2626 return sprintf( '/*@%s@*/ %s', $cmnt, $submatches->[1] );
337             }
338 171         576 return sprintf( " %s", $submatches->[1] );
339 31         106 };
340              
341 31         74 foreach my $reggrp ( @REGGRPS ) {
342 217         91623 my $reggrp_args = { reggrp => $self->{$reggrp}->{reggrp_data} };
343              
344 217 100 100     763 $reggrp_args->{restore_pattern} = $RESTORE_PATTERN if ( $reggrp eq 'data_store' or $reggrp eq 'concat_store' );
345              
346 217         522 $self->{ '_reggrp_' . $reggrp } = Regexp::RegGrp->new( $reggrp_args );
347             }
348              
349 31         5526 $self->{block_data} = [];
350              
351 31         154 return $self;
352             }
353              
354             sub minify {
355 42     42 0 6853 my ( $self, $input, $opts );
356              
357 42 100 66     178 unless (ref( $_[0] )
358             and ref( $_[0] ) eq __PACKAGE__ )
359             {
360 9         29 $self = __PACKAGE__->init();
361              
362 9 50       30 shift( @_ ) unless ( ref( $_[0] ) );
363              
364 9         17 ( $input, $opts ) = @_;
365             }
366             else {
367 33         63 ( $self, $input, $opts ) = @_;
368             }
369              
370 42 50       102 if ( ref( $input ) ne 'SCALAR' ) {
371 0         0 carp( 'First argument must be a scalarref!' );
372 0         0 return undef;
373             }
374              
375 42         60 my $javascript = \'';
376 42         56 my $cont = 'void';
377              
378 42 50       80 if ( defined( wantarray ) ) {
379 0 0       0 my $tmp_input = ref( $input ) ? ${$input} : $input;
  0         0  
380              
381 0         0 $javascript = \$tmp_input;
382 0         0 $cont = 'scalar';
383             }
384             else {
385 42 50       81 $javascript = ref( $input ) ? $input : \$input;
386             }
387              
388 42 100       78 if ( ref( $opts ) eq 'HASH' ) {
389 27         43 foreach my $field ( @BOOLEAN_ACCESSORS ) {
390 54 100       129 $self->$field( $opts->{$field} ) if ( defined( $opts->{$field} ) );
391             }
392              
393 27         42 foreach my $field ( 'compress', 'copyright' ) {
394 54 100       147 $self->$field( $opts->{$field} ) if ( defined( $opts->{$field} ) );
395             }
396             }
397              
398             # (re)initialize variables used in the closures
399 42         85 $reggrp_comments = $self->reggrp_comments;
400 42         83 $reggrp_clean = $self->reggrp_clean;
401 42         87 $reggrp_whitespace = $self->reggrp_whitespace;
402              
403 42         58 my $copyright_comment = '';
404              
405 42 100       51 if ( ${$javascript} =~ /$COPYRIGHT_COMMENT/ism ) {
  42         1910  
406 3         22 $copyright_comment = $1;
407             }
408              
409             # Resets copyright_comment() if there is no copyright comment
410 42         114 $self->_copyright_comment( $copyright_comment );
411              
412 42 100 100     84 if ( not $self->no_compress_comment() and ${$javascript} =~ /$PACKER_COMMENT/ ) {
  37         615  
413 1         3 my $compress = $1;
414 1 50       4 if ( $compress eq '_no_compress_' ) {
415 1 50       4 return ${$javascript} if ( $cont eq 'scalar' );
  0         0  
416 1         3 return;
417             }
418              
419 0         0 $self->compress( $compress );
420             }
421              
422 41         55 ${$javascript} =~ s/\r//gsm;
  41         118  
423 41         45 ${$javascript} .= "\n";
  41         625  
424 41         90 $self->reggrp_comments()->exec( $javascript );
425 41         8457 $self->reggrp_clean()->exec( $javascript );
426 41         10583 $self->reggrp_whitespace()->exec( $javascript );
427 41         195677 $self->reggrp_concat_store()->exec( $javascript );
428 41         2856 $self->reggrp_concat()->exec( $javascript );
429 41         13307 $self->reggrp_concat_store()->restore_stored( $javascript );
430              
431 41 100       18864 if ( $self->compress() ne 'clean' ) {
432 17         35 $self->reggrp_data_store()->exec( $javascript );
433              
434 17         1421 while ( ${$javascript} =~ /$SHRINK_VARS->{BLOCK}/ ) {
  44         3131  
435 27         42 ${$javascript} =~ s/$SHRINK_VARS->{BLOCK}/$self->_store_block_data( $1 )/egsm;
  27         3071  
  532         994  
436             }
437              
438 17         47 $self->_restore_data( $javascript, 'block_data', $SHRINK_VARS->{ENCODED_BLOCK} );
439              
440 17         24 my %shrunk_vars = map { $_ => 1 } ( ${$javascript} =~ /$SHRINK_VARS->{SHRUNK}/g );
  1659         2207  
  17         768  
441              
442 17         173 my $cnt = 0;
443 17         57 foreach my $shrunk_var ( sort keys( %shrunk_vars ) ) {
444 58         104 my $short_id;
445             do {
446 70         124 $short_id = $self->_encode52( $cnt++ );
447 58         63 } while ( ${$javascript} =~ /[^a-zA-Z0-9_\\x24\.]\Q$short_id\E[^a-zA-Z0-9_\\x24:]/ );
  70         2626  
448              
449 58         104 ${$javascript} =~ s/$shrunk_var/$short_id/g;
  58         1383  
450             }
451              
452 17         40 $self->reggrp_data_store()->restore_stored( $javascript );
453              
454 17         4688 $self->{block_data} = [];
455             }
456              
457 41 100 100     81 if ( $self->compress() eq 'obfuscate' or $self->compress() eq 'best' ) {
458 7         11 my $words = {};
459              
460 7         11 my @words = ${$javascript} =~ /$BASE62_VARS->{WORDS}/g;
  7         8054  
461              
462 7         47 my $idx = 0;
463              
464 7         15 foreach ( @words ) {
465 10898         14776 $words->{$_}->{count}++;
466             }
467              
468 7         12 WORD: foreach my $word ( sort { $words->{$b}->{count} <=> $words->{$a}->{count} } sort keys( %{$words} ) ) {
  10427         12647  
  7         1455  
469              
470 3120 100 66     5789 if ( exists( $words->{$word}->{encoded} ) and $words->{$word}->{encoded} eq $word ) {
471 151         192 next WORD;
472             }
473              
474 2969         3816 my $encoded = $self->_encode62( $idx );
475              
476 2969 100       4727 if ( exists( $words->{$encoded} ) ) {
477 209         220 my $next = 0;
478 209 100       320 if ( exists( $words->{$encoded}->{encoded} ) ) {
479 58         93 $words->{$word}->{encoded} = $words->{$encoded}->{encoded};
480 58         77 $words->{$word}->{index} = $words->{$encoded}->{index};
481 58         79 $words->{$word}->{minus} = length( $word ) - length( $words->{$word}->{encoded} );
482 58         67 $next = 1;
483             }
484 209         267 $words->{$encoded}->{encoded} = $encoded;
485 209         233 $words->{$encoded}->{index} = $idx;
486 209         261 $words->{$encoded}->{minus} = 0;
487 209         221 $idx++;
488 209 100       303 next WORD if ( $next );
489 151         182 redo WORD;
490             }
491              
492 2760         3630 $words->{$word}->{encoded} = $encoded;
493 2760         3103 $words->{$word}->{index} = $idx;
494 2760         3243 $words->{$word}->{minus} = length( $word ) - length( $encoded );
495              
496 2760         3388 $idx++;
497             }
498              
499 7         204 my $packed_length = length( ${$javascript} );
  7         27  
500              
501 7         16 my ( @pk, @pattern ) = ( (), () );
502              
503 7         9 foreach ( sort { $words->{$a}->{index} <=> $words->{$b}->{index} } sort keys( %{$words} ) ) {
  12081         14955  
  7         1474  
504 2969         4675 $packed_length -= ( $words->{$_}->{count} * $words->{$_}->{minus} );
505              
506 2969 100       4113 if ( $words->{$_}->{encoded} ne $_ ) {
507 2760         3333 push( @pk, $_ );
508 2760         4140 push( @pattern, $words->{$_}->{encoded} );
509             }
510             else {
511 209         239 push( @pk, '' );
512 209         258 push( @pattern, '' );
513             }
514             }
515              
516 7         143 my $size = scalar( @pattern );
517              
518 7 100       123 splice( @pattern, 62 ) if ( scalar( @pattern ) > 62 );
519              
520 7         31 my $pd = join( '|', @pattern );
521              
522 7         20 $self->reggrp_trim()->exec( \$pd );
523              
524 7 50       874 unless ( $pd ) {
525 0         0 $pd = '^$';
526             }
527             else {
528 7         16 $pd = '[' . $pd . ']';
529              
530 7 100       19 if ( $size > 62 ) {
531 3         7 $pd = '(' . $pd . '|';
532              
533 3         9 my $enc = $self->_encode62( $size );
534              
535 3         10 my ( $c ) = $enc =~ /(^.)/;
536 3         7 my $ord = ord( $c );
537              
538 3         6 my $mul = length( $enc ) - 1;
539              
540 3         6 my $is62 = 0;
541              
542 3 100       13 if ( $ord >= 65 ) {
    100          
    50          
    50          
543 1 50       5 if ( $c eq 'Z' ) {
544 0         0 $mul += 1;
545 0         0 $is62 = 1;
546             }
547             else {
548 1         2 $pd .= '[0-9a';
549 1 50       8 if ( $ord > 97 ) {
    50          
    50          
550 0         0 $pd .= '-' . $c;
551             }
552             elsif ( $ord > 65 ) {
553 0         0 $pd .= '-zA-' . $c;
554             }
555             elsif ( $ord == 65 ) {
556 1         2 $pd .= '-zA';
557             }
558 1         2 $pd .= ']';
559             }
560             }
561             elsif ( $ord == 57 ) {
562 1         2 $pd .= '[0-9]';
563             }
564             elsif ( $ord == 50 ) {
565 0         0 $pd .= '[12]';
566             }
567             elsif ( $ord == 49 ) {
568 1         2 $pd .= '1';
569             }
570             else {
571 0         0 $pd .= '[0-' . ( $ord - 48 ) . ']';
572             }
573              
574 3 50       10 $pd .= '[0-9a-zA-Z]' . ( ( $mul > 1 ) ? '{' . $mul . '}' : '' );
575              
576 3 50       13 $mul-- if ( $is62 );
577              
578 3 50       6 if ( $mul > 1 ) {
579 0         0 for ( my $i = $mul; $i >= 2; $i-- ) {
580 0         0 $pd .= '|[0-9a-zA-Z]{' . $i . '}';
581             }
582             }
583              
584 3         6 $pd .= ')';
585             }
586             }
587 7         16 $packed_length += length( $pd );
588              
589 7         248 my $pk = join( '|', @pk );
590 7         328 $pk =~ s/(?>\|+)$//;
591 7         15 $packed_length += length( $pk );
592              
593 7 50       42 my $pc = length( $pk ) ? ( ( $pk =~ tr/|/|/ ) + 1 ) : 0;
594 7         14 $packed_length += length( $pc );
595              
596 7         13 my $pa = '[]';
597 7         12 $packed_length += length( $pa );
598              
599 7 50       66 my $pe = $BASE62_VARS->{ 'ENCODE' . ( $pc > 10 ? $pc > 36 ? 62 : 36 : 10 ) };
    100          
600 7         13 $packed_length += length( $pe );
601              
602 7         13 $packed_length += length( $BASE62_VARS->{UNPACK} );
603 7         82 $packed_length -= ( $BASE62_VARS->{UNPACK} =~ s/(%s|%d)/$1/g ) * 2;
604              
605 7         16 my ( @length_matches ) = ${$javascript} =~ s/((?>[\r\n]+))/$1/g;
  7         201  
606 7         19 foreach ( @length_matches ) {
607 7         17 $packed_length -= length( $_ ) - 3;
608             }
609              
610 7         12 $packed_length += ${$javascript} =~ tr/\\\'/\\\'/;
  7         72  
611              
612 7 100 100     18 if ( $self->compress() eq 'obfuscate' or $packed_length <= length( ${$javascript} ) ) {
  5         41  
613              
614 5         8 ${$javascript} =~ s/$BASE62_VARS->{WORDS}/sprintf( "%s", $words->{$1}->{encoded} )/eg;
  5         35  
  10865         27146  
615              
616 5         11 ${$javascript} =~ s/([\\'])/\\$1/g;
  5         314  
617 5         10 ${$javascript} =~ s/[\r\n]+/\\n/g;
  5         101  
618              
619 5         9 my $pp = ${$javascript};
  5         17  
620              
621 5         107 ${$javascript} = sprintf( $BASE62_VARS->{UNPACK}, $pe, $pd, $pp, $pa, $pc, $pk );
  5         1971  
622             }
623              
624             }
625              
626 41 100       118 if ( not $self->remove_copyright() ) {
627 40   100     84 ${$javascript} = ( $self->copyright() || $self->_copyright_comment() ) . ${$javascript};
  40         101  
  40         1196  
628             }
629              
630             # GH #9 bodge for sourceMappingURL
631 41         70 ${$javascript} =~ s/__NEW_LINE__/\n/xsmg;
  41         255  
632 41         66 ${$javascript} =~ s!//#sourceMappingURL!//# sourceMappingURL!g;
  41         434  
633 41         51 chomp( ${$javascript} );
  41         76  
634              
635 41 50       472 return ${$javascript} if ( $cont eq 'scalar' );
  0         0  
636             }
637              
638             sub _restore_data {
639 379     379   578 my ( $self, $string_ref, $data_name, $pattern ) = @_;
640              
641 379         439 while ( ${$string_ref} =~ /$pattern/ ) {
  537         1807  
642 158         220 ${$string_ref} =~ s/$pattern/$self->{$data_name}->[$1]/egsm;
  158         568  
  532         1955  
643             }
644             }
645              
646             sub _store_block_data {
647 532     532   1182 my ( $self, $match ) = @_;
648              
649 532         3339 my ( undef, $prefix, $blocktype, $args, $block ) = $match =~ /$SHRINK_VARS->{BLOCK}/;
650              
651 532   100     1253 $prefix ||= '';
652 532   100     874 $blocktype ||= '';
653 532   100     862 $args ||= '';
654 532         596 my $replacement = '';
655 532 100       800 if ( $blocktype eq 'function' ) {
656              
657 181         414 $self->_restore_data( \$block, 'block_data', $SHRINK_VARS->{SCOPED} );
658              
659 181         1281 $args =~ s/\s*//g;
660              
661 181         397 $block = $args . $block;
662 181         867 $prefix =~ s/$SHRINK_VARS->{BRACKETS}//;
663              
664 181         658 $args =~ s/^\(|\)$//g;
665              
666 181         564 while ( $args =~ /$SHRINK_VARS->{CALLER}/ ) {
667 0         0 $args =~ s/$SHRINK_VARS->{CALLER}//gsm;
668             }
669              
670 181         607 my @vars = grep( $_, split( /\s*,\s*/, $args ) );
671 181 100       372 my $do_shrink = grep( $_ eq '_no_shrink_', @vars ) ? 0 : 1;
672              
673 181         250 my %block_vars = ();
674 181 100       276 if ( $do_shrink ) {
675 180         1818 %block_vars = map { $_ => 1 } ( $block =~ /$SHRINK_VARS->{VARS}/g ), grep( $_ ne '$super', @vars );
  475         978  
676             }
677              
678 181         468 $self->_restore_data( \$block, 'block_data', $SHRINK_VARS->{ENCODED_BLOCK} );
679              
680 181 100       308 if ( $do_shrink ) {
681              
682 180         222 my $cnt = 0;
683 180         528 foreach my $block_var ( sort keys( %block_vars ) ) {
684 468 50       732 if ( length( $block_var ) ) {
685 468         3469 while ( $block =~ /$SHRINK_VARS->{PREFIX}\Q$cnt\E\b/ ) {
686 16         121 $cnt++;
687             }
688              
689 468         8389 while ( $block =~ /[^a-zA-Z0-9_\\x24\.]\Q$block_var\E[^a-zA-Z0-9_\\x24:]/ ) {
690 491         8306 $block =~ s/([^a-zA-Z0-9_\\x24\.])\Q$block_var\E([^a-zA-Z0-9_\\x24:])/sprintf( "%s\x02%d%s", $1, $cnt, $2 )/eg;
  1658         7200  
691             }
692              
693 468         4570 $block =~ s/([^{,a-zA-Z0-9_\\x24\.])\Q$block_var\E:/sprintf( "%s\x02%d:", $1, $cnt )/eg;
  1         4  
694              
695 468         936 $cnt++;
696             }
697             }
698             }
699 181         564 $replacement = sprintf( "%s~%d~", $prefix, scalar( @{ $self->{block_data} } ) );
  181         507  
700              
701 181         237 push( @{ $self->{block_data} }, $block );
  181         474  
702             }
703             else {
704 351         368 $replacement = sprintf( "~#%d~", scalar( @{ $self->{block_data} } ) );
  351         735  
705              
706 351         429 push( @{ $self->{block_data} }, $prefix . $block );
  351         698  
707             }
708              
709 532         16772 return $replacement;
710             }
711              
712             sub _encode52 {
713 70     70   108 my ( $self, $c ) = @_;
714              
715 70         84 my $m = $c % 52;
716              
717 70 50       136 my $ret = $m > 25 ? chr( $m + 39 ) : chr( $m + 97 );
718              
719 70 50       124 if ( $c >= 52 ) {
720 0         0 $ret = $self->_encode52( int( $c / 52 ) ) . $ret;
721             }
722              
723 70 50       100 $ret = substr( $ret, 1 ) . '0' if ( $ret =~ /^(do|if|in)$/ );
724              
725 70         141 return $ret;
726             }
727              
728             sub _encode62 {
729 5721     5721   6883 my ( $self, $c ) = @_;
730              
731 5721         5900 my $m = $c % 62;
732              
733 5721 100       8448 my $ret = $m > 35 ? chr( $m + 29 ) : $m > 9 ? chr( $m + 87 ) : $m;
    100          
734              
735 5721 100       7609 if ( $c >= 62 ) {
736 2749         4062 $ret = $self->_encode62( int( $c / 62 ) ) . $ret;
737             }
738              
739 5721         9205 return $ret;
740             }
741              
742             1;
743              
744             __END__