#!/usr/bin/perl -w
use strict;
use Getopt::Long qw(:config no_ignore_case bundling);
use File::Basename;
use utf8;
my $ifname = '';
my $ofname = '';
my $stdio = '';
my $last = '';
my $version = "@VERSION@";
my ( $if, $of );
sub usage
{
my $p = basename( $0 );
print <<EOF;
Usage: $p [options] [input_file_name]
Options:
--output | -o - output file name;
--version | -v - print version numver;
--help | -h | -? - print this message;
-- - an option terminator;
- - use std{io|out} instead of files.
Examples:
Input file is 'full.json', output file is 'min.json':
\$ $p -o min.json full.json
Input file is 'STDIN', output file is 'min.json':
\$ $p -o min.json -
Please note that to terminate your input by keyboard you have to use
<Ctrl>+d combination;
Input file is 'full.json', output file is 'STDOUT':
\$ $p -- full.json
Use stdin, stdout:
\$ $p - < full.json > min.json
Enjoj.
EOF
exit;
}
sub version
{
print <<EOF;
$version
EOF
exit;
}
my $a = '';
my $b = '';
my $x = '';
my $y = '';
my $lookahead = 0;
sub get
{
my $c = 0;
$c = $lookahead;
$lookahead = 0;
if( $c == 0 )
{
if( ! eof( $if ) )
{
my $ch = getc( $if );
$c = ord( $ch );
}
else
{
$c = 0;
}
}
if( $c >= ord(" ") || $c == ord("\n") || $c == 0 )
{
return $c;
}
if( $c == ord("\r") )
{
return ord("\n");
}
return ord(" ");
}
sub peek
{
$lookahead = get();
return $lookahead;
}
sub next_cn
{
my $c = get();
if( $c == ord("/") )
{
my $char = chr( peek() );
if( $char eq "/" )
{
for(;;) { $c = get(); if( $c <= ord("\n") ) { last; } }
}
elsif( $char eq "*" )
{
get();
while( $c != ord(" ") )
{
my $cn = get();
if( chr( $cn ) eq "*" )
{
if( chr( peek() ) eq "/" ) { get(); $c = ord(" "); }
}
elsif( $cn == 0 )
{
print STDERR "Unterminated comment.\n"; exit 1;
}
}
}
}
$y = $x;
$x = chr( $c );
return $c;
}
sub action
{
my $d = $_[0];
if( $d == 1 )
{
print $of $a;
if(
( $y eq "\n" || $y eq " " ) &&
( $a eq "+" || $a eq "-" || $a eq "*" || $a eq "/" ) &&
( $b eq "+" || $b eq "-" || $b eq "*" || $b eq "/" )
)
{
print $of $y;
}
$d = 2;
}
if( $d == 2 )
{
$a = $b;
if( $a eq "\'" || $a eq "\"" || $a eq "`" )
{
for(;;)
{
my $c;
print $of $a;
$c = get(); $a = chr( $c );
if( $a eq $b ) { last; }
if( $a eq "\\" )
{
print $of $a;
$c = get(); $a = chr( $c );
}
if( $c == 0 )
{
print STDERR "Unterminated string literal.\n"; exit 1;
}
}
}
$d = 3;
}
if( $d == 3 )
{
$b = chr( next_cn() );
if( $b eq "/" &&
( $a eq "(" || $a eq "," || $a eq "=" || $a eq ":" ||
$a eq "[" || $a eq "!" || $a eq "&" || $a eq "|" ||
$a eq "?" || $a eq "+" || $a eq "-" || $a eq "~" ||
$a eq "*" || $a eq "/" || $a eq "{" || $a eq "\n"
)
)
{
print $of $a;
if( $a eq "/" || $a eq "*" ) { print $of " "; }
print $of $b;
for(;;)
{
my $c;
$c = get(); $a = chr( $c );
if( $a eq "[" )
{
for(;;)
{
print $of $a;
$c = get(); $a = chr( $c );
if( $a eq "]" )
{
last;
}
if( $a eq "\\" )
{
print $of $a;
$c = get(); $a = chr( $c );
}
if( $c == 0 )
{
print STDERR "Unterminated regular expression literal.\n"; exit 1;
}
}
}
elsif( $a eq "/" )
{
my $ch = chr( peek() );
if( $ch eq "/" || $ch eq "*" )
{
print STDERR "Unterminated regular expression literal.\n"; exit 1;
}
last;
}
elsif( $a eq "\\" )
{
print $of $a;
$c = get(); $a = chr( $c );
}
elsif( $c == 0 )
{
print STDERR "Unterminated regular expression literal.\n"; exit 1;
}
print $of $a;
}
$b = chr( next_cn() );
}
}
}
sub isalnum
{
my $ch = $_[0];
if( ($ch ge "a" && $ch le "z") || ($ch ge "0" && $ch le "9") ||
($ch ge "A" && $ch le "Z") || $ch eq "_" || $ch eq "\$" ||
$ch eq "\\" || $ch gt "~"
) { return 1; } else { return 0; }
}
sub jsmin
{
if( peek() == 0xEF ) { get(); get(); get(); }
$a = "\n";
action( 3 );
while( $a ne "" && $a ne "\0" )
{
if( $a eq " " )
{
action( isalnum( $b ) ? 1 : 2 );
}
elsif( $a eq "\n" )
{
if( $b eq "{" || $b eq "[" || $b eq "(" || $b eq "+" ||
$b eq "-" || $b eq "!" || $b eq "~"
)
{
action( 1 );
}
elsif( $b eq " " )
{
action( 3 );
}
else
{
action( isalnum( $b ) ? 1 : 2 );
}
}
else
{
if( $b eq " " )
{
action( isalnum( $a ) ? 1 : 3 );
}
elsif( $b eq "\n" )
{
if( $a eq "}" || $a eq "]" || $a eq ")" || $a eq "+" ||
$a eq "-" || $a eq "\"" || $a eq "\'" || $a eq "`"
)
{
action( 1 );
}
else
{
action( isalnum( $a ) ? 1 : 3 );
}
}
else
{
action( 1 );
}
}
}
#lats carriage return
print $of "\n";
}
local $SIG{__WARN__} = sub {
my $message = shift;
print STDERR "ERROR: " . $message;
usage();
};
if( ! GetOptions( 'o=s' => \$ofname,
'output=s' => \$ofname,
'' => \$stdio,
'help|h|?' => sub { usage() },
'version|v' => sub { version() }
)
)
{
usage();
}
foreach( @ARGV )
{
$last = $_;
if( $#ARGV )
{
usage();
}
# NOTE: The '--' is an option terminator!
# So ./script -- - returns last equal to '-'.
#
# The $last argument is a ifname by default;
#
}
if( $ifname eq '' && $ofname eq '' && ! $stdio )
{
usage();
}
if( $ofname ne '' && $stdio && $last ne '' )
{
# like that:
# ./script -o min.json full.json -
# ./script -o min.json - full.json
#
print "ERROR: Input file defined twice: as 'stdin' and as a orfinary file '$last'\n";
usage();
}
else
{
$ifname = $last; $last = '';
}
if( $ofname eq '' ) { $ofname = '-'; }
if( $ifname eq '' ) { $ifname = '-'; }
if( $ifname eq '-' ) { $if = *STDIN; } else { open( $if, "< $ifname" ); }
if( $ofname eq '-' ) { $of = *STDOUT; } else { open( $of, "> $ofname" ); }
jsmin();
close $if;
close $of;
exit 0;