JavaScript, Json minimizator

jsmin – is a JavaScript, Json minimizator which removes comments and unnecessary whitespace from JS, JSON files

2 Commits   0 Branches   0 Tags
#!/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;