diff options
-rw-r--r-- | .gitignore | 25 | ||||
-rw-r--r-- | AUTHORS | 3 | ||||
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | Makefile.am | 11 | ||||
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | README | 3 | ||||
-rw-r--r-- | acsite.m4 | 56 | ||||
-rwxr-xr-x | auto-clean | 40 | ||||
-rwxr-xr-x | bootstrap | 6 | ||||
-rw-r--r-- | configure.ac | 165 | ||||
-rw-r--r-- | doc/README | 47 | ||||
-rw-r--r-- | doc/cross-compilation.txt | 13 | ||||
-rw-r--r-- | perl/Makefile.am | 7 | ||||
-rwxr-xr-x | perl/jsmin.pl.in | 389 | ||||
-rw-r--r-- | src/Makefile.am | 6 | ||||
-rw-r--r-- | src/main.c | 533 |
16 files changed, 1311 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fe6a6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ + +autom4te.cache/ + +Makefile +Makefile.in +config.h +config.h.in +config.log +config.status +compile +config.guess +config.sub +configure +install-sh +missing +stamp-h1 +aclocal.m4 +depcomp + +perl/Makefile.in + +src/.deps/ +src/Makefile.in + +*~ @@ -0,0 +1,3 @@ + +Douglas Crockford (www.crockford.com) +Andrey V.Kosteltsev (radix.pro) diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..5dac465 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,4 @@ +2015-09-24 Andrey V.Kosteltsev <kx@radix.pro> + + * all-files: The first release of jsmin with using getopt functionality. + Perl version also present. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..7844b8d --- /dev/null +++ b/Makefile.am @@ -0,0 +1,11 @@ + + +# +# In compilation order. +# ==================== +# +SUBDIRS = perl src + + +EXTRA_DIST = doc auto-clean .gitignore bootstrap + @@ -0,0 +1,3 @@ + +24 Sep 2015 - first release with getopt functionality. + Perl version also present. @@ -0,0 +1,3 @@ + +jsmin - is a JavaScript, Json minimizator which removes +comments and unnecessary whitespace from JS, JSON files. diff --git a/acsite.m4 b/acsite.m4 new file mode 100644 index 0000000..9840042 --- /dev/null +++ b/acsite.m4 @@ -0,0 +1,56 @@ + +dnl ============================================================ +dnl Display Configuration Headers +dnl +dnl configure.in: +dnl AC_MSG_CFG_PART(<text>) +dnl ============================================================ + +define(AC_MSG_CFG_PART,[dnl + AC_MSG_RESULT() + AC_MSG_RESULT(${TB}$1:${TN}) +])dnl + + +AC_DEFUN(AC_JSMIN_HEADLINE,[dnl + # Use a `Quadrigaph'. @<:@ gives you [ and @:>@ gives you ] : + TB=`echo -n -e '\033@<:@1m'` + TN=`echo -n -e '\033@<:@0m'` + echo "" + echo "Configuring ${TB}$1${TN} ($2), Version ${TB}${PACKAGE_VERSION}${TN}" + echo "$3" +])dnl + + +dnl ============================================================ +dnl Test for build_host `ln -s' . +dnl ============================ +dnl +dnl Usage: +dnl ----- +dnl AC_PATH_PROG_LN_S +dnl AC_SUBST(LN) +dnl AC_SUBST(LN_S) +dnl +dnl ============================================================ +AC_DEFUN(AC_PATH_PROG_LN_S, +[AC_PATH_PROG(LN, ln, no, /usr/local/bin:/usr/bin:/bin:$PATH) +AC_MSG_CHECKING(whether ln -s works on build host) +AC_CACHE_VAL(ac_cv_path_prog_LN_S, +[rm -f conftestdata +if $LN -s X conftestdata 2>/dev/null +then + rm -f conftestdata + ac_cv_path_prog_LN_S="$LN -s" +else + ac_cv_path_prog_LN_S="$LN" +fi])dnl +LN_S="$ac_cv_path_prog_LN_S" +if test "$ac_cv_path_prog_LN_S" = "$LN -s"; then + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi +AC_SUBST(LN)dnl +AC_SUBST(LN_S)dnl +]) diff --git a/auto-clean b/auto-clean new file mode 100755 index 0000000..84d1b59 --- /dev/null +++ b/auto-clean @@ -0,0 +1,40 @@ +#!/bin/bash + +CWD=`pwd` + +program=`basename $0` + +usage() { + cat << EOF + +Usage: $program [options] + +Options: + -h,--help Display this message. + +EOF +} + +if [ -f "${CWD}/Makefile" ] ; then + make distclean +fi + +gitignore='.gitignore' + +while read ln; do + line=`echo "${ln}" | sed 's,^[ \t],,' | sed 's,[ \t]$,,'` + if [ "x$line" != "x" -a "${line:0:1}" != "#" ] ; then + if `echo "${line}" | grep -q '\*~$'` ; then + find "`dirname "${line}"`" -type f -iname '*~' -print0 | while IFS= read -r -d '' file ; do + rm -f "$file" + done + elif `echo "${line}" | grep -q '\*'` ; then + find "`dirname "${line}"`" -type f -iname "`basename "${line}"`" -print0 | while IFS= read -r -d '' file ; do + rm -f "$file" + done + else + if [ -d "${line}" ] ; then rm -rf "${line}" ; fi + if [ -f "${line}" ] ; then rm -f "${line}" ; fi + fi + fi +done < ${CWD}/${gitignore} diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..22100eb --- /dev/null +++ b/bootstrap @@ -0,0 +1,6 @@ +#!/bin/sh + +aclocal +autoheader +automake --gnu --add-missing --copy +autoconf diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..6bdc349 --- /dev/null +++ b/configure.ac @@ -0,0 +1,165 @@ + +dnl ============================================================ +dnl Process this file with autoconf to produce +dnl a configure script. +dnl ============================================================ + +AC_PREREQ(2.71)dnl dnl Minimum Autoconf version required. + +AC_INIT([jsmin],[0.0.1], + [support@radix.pro],[jsmin],[http://radix.pro]) + +AC_JSMIN_HEADLINE([jsmin], [JSmin], [Copyright (c) 2015-2023 Andrey V.Kosteltsev]) + + +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl $$ $$ +dnl $$ PART: Init Automake environment $$ +dnl $$ $$ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +AC_MSG_CFG_PART(Init Automake environment) + +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE([subdir-objects foreign no-dist-gzip dist-xz tar-pax]) + +AC_CONFIG_HEADERS([config.h]) + +AC_PREFIX_DEFAULT(/usr/local) + + +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl $$ $$ +dnl $$ PART: Test for Build Tools $$ +dnl $$ $$ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +AC_MSG_CFG_PART(Test for Build Tools) +AC_CHECK_TOOL([GCC], [gcc], [:]) + + +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl $$ $$ +dnl $$ PART: Test for Auxiliary (my be version sensitive) $$ +dnl $$ programs $$ +dnl $$ $$ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +AC_MSG_CFG_PART(Test for Auxiliary (my be version sensitive) programs) + +dnl ============================================================ +dnl Locate tools( on build machine! ) . +dnl ================= +dnl ============================================================ +AC_PATH_PROG_LN_S +AC_SUBST(LN) +AC_SUBST(LN_S) + +dnl Нам нужен исполняемый файл pwd. Встроенный "pwd -P" нас +dnl не устраивает( из соображений переносимости ) . +AC_PATH_PROG(PWD_P, pwd, no) +if test "$PWD_P" = no; then + AC_MSG_ERROR(******** A pwd binary could not be found.) +fi + +test -n "$aux_missing" && AC_MSG_WARN([ +******** These auxiliary programs are missing or too old: $aux_missing +******** some features will be disabled. +******** Check the INSTALL file for required versions.]) + + +dnl +dnl Remove TAR option '-o' for allow long file names in DIST archive. +dnl Standard tar.m4 from aclocal-1.9 package probvides following +dnl 'am__tar' command: +dnl ${AMTAR} chof - "$$tardir" +dnl and uses '-chof' options for backward compatibility. +dnl +am__tar='${AMTAR} chf - "$$tardir"' +AC_SUBST(am__tar) + +AC_PATH_PROGS(TAR, tar gtar, no, /usr/local/bin:/usr/bin:/bin:$PATH) + + + +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl $$ $$ +dnl $$ PART: Build Parameters $$ +dnl $$ $$ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +AC_MSG_CFG_PART(Build Parameters) + +dnl +dnl Check for system header files. +dnl ============================= +dnl /* GetText это проверил. А мы делаем для себя. */ +AC_HEADER_STAT +AC_CHECK_HEADERS(fcntl.h unistd.h stdlib.h stdio.h errno.h string.h getopt.h) + + +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl $$ $$ +dnl $$ PART: OUTPUT Substitution $$ +dnl $$ $$ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +dnl ============================================================ +AC_MSG_CFG_PART(OUTPUT) + + +AC_CONFIG_FILES([ +Makefile +perl/Makefile +perl/jsmin.pl +src/Makefile +]) +AC_OUTPUT + +chmod 0755 perl/jsmin.pl + +if test -f "Makefile"; then + echo "" + echo "Now please type:" + echo " \`${TB}make${TN}' to compile," + echo " \`${TB}make install${TN}' to make and install ${TB}jsmin${TN}," + echo " \`${TB}make dist${TN}' to create distributable tarball, or" + echo " \`${TB}make distclean${TN}' to clean befor configure for another target." + echo "Enjoy." + echo "" +fi diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..4d34754 --- /dev/null +++ b/doc/README @@ -0,0 +1,47 @@ + +/* + This file contant RUSSIAN letters( code-page: utf-8 ) + ***************************************************************/ + +/* begin * + + Создание дистрибутива: + ===================== + + 1. Создание (в корне) файлов: + ../AUTHORS + ../ChangeLog + ../NEWS + ../README + это произвольным образом, на данном этапе + необходимо лишь их наличие (для automake). + + 2. Создание (в корне) файлов: ++ ../Makefile.am ++ ../acsite.m4 ++ ../configure.ac ++ ../perl/Makefile.am ++ ../src/Makefile.am + это фактически и есть разработка средств + компиляции, инсталляции и, в конечном итоге, + создание дистрибутива. + + 3. Выполнил script bootstrap: + $ cd .. + $ ./bootstrap + + + Изменение версии дистрибутива: + ============================= + + 1. В файле configure.ac меняем определение + AC_INIT([jsmin],[0.0.1], + [support@radix.pro],[jsmin],[http://radix.pro]) + на + AC_INIT([jsmin],[0.0.2], + [support@radix.pro],[jsmin],[http://radix.pro]) + . + + Затем выполняем скрипт bootstrap . + + * end */ diff --git a/doc/cross-compilation.txt b/doc/cross-compilation.txt new file mode 100644 index 0000000..4bebf9a --- /dev/null +++ b/doc/cross-compilation.txt @@ -0,0 +1,13 @@ + +This is an example: + + $ tar xJvf jsmin-0.0.1.tar.xz + $ cd jsmin-0.0.1 + + $ CC=/opt/toolchain/mipsel-BCM74X-linux-eglibc/1.0.8/bin/mipsel-bcm74x-linux-gnu-gcc \ + ./configure --prefix=/usr \ + --target=mipsel-bcm74x-linux-gnu \ + --host=mipsel-bcm74x-linux-gnu + + $ make + $ make install [DESTDIR=dir] diff --git a/perl/Makefile.am b/perl/Makefile.am new file mode 100644 index 0000000..7cd8778 --- /dev/null +++ b/perl/Makefile.am @@ -0,0 +1,7 @@ + +############################################################ +# Don't strip jsmin.pl script when `make install-strip' # +############################################################ +INSTALL_SCRIPT = ${INSTALL} + +bin_SCRIPTS = jsmin.pl diff --git a/perl/jsmin.pl.in b/perl/jsmin.pl.in new file mode 100755 index 0000000..ff648b2 --- /dev/null +++ b/perl/jsmin.pl.in @@ -0,0 +1,389 @@ +#!/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; diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..f08fea3 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,6 @@ + +bin_PROGRAMS = jsmin + +jsmin_SOURCES = main.c + +jsmin_CFLAGS = -Wall -funsigned-char -I$(top_srcdir) diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..a26fff8 --- /dev/null +++ b/src/main.c @@ -0,0 +1,533 @@ + +#ifdef HAVE_CONFIG_H +#include <config.h> + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STDIO_H +#include <stdio.h> +#endif +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + +#else +/************************************************************** + For building outside from source package as a single C file: + + $ gcc -o jsmin main.c + */ + +#include <fcntl.h> +#include <unistd.h> + +#include <sys/stat.h> + +#include <errno.h> +#include <string.h> + +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> + +#endif + +static char *ifname = NULL; +static char *ofname = NULL; + +FILE *ifile; +FILE *ofile; +FILE *tmp; + +static char *progname = NULL; + +static int opt_usage; +static int opt_version; + +#define DIR_SEPARATOR '/' + +static void usage() +{ + printf( "\n" ); + printf( "Usage: %s [options] [input_file_name]\n", progname ); + printf( "Options:\n" ); + printf( " --output | -o - output file name;\n" ); + printf( " --version | -v - print version numver;\n" ); + printf( " --help | -h | -? - print this message;\n" ); + printf( " -- - an option terminator;\n" ); + printf( " - - use std{io|out} instead of files.\n\n" ); + printf( "Examples:\n\n" ); + printf( " Input file is 'full.json', output file is 'min.json':\n\n" ); + printf( " $ %s -o min.json full.json\n\n", progname ); + printf( " Input file is 'STDIN', output file is 'min.json':\n\n" ); + printf( " $ %s -o min.json -\n\n", progname ); + printf( " Please note that to terminate your input by keyboard you have to use\n" ); + printf( " <Ctrl>+d combination;\n\n" ); + printf( " Input file is 'full.json', output file is 'STDOUT':\n\n" ); + printf( " $ %s -- full.json\n\n", progname ); + printf( " Use stdin, stdout:\n\n" ); + printf( " $ %s - < full.json > min.json\n\n", progname ); + printf( "Enjoj.\n\n" ); + + exit( 1 ); +} + +static void version() +{ +#ifdef HAVE_CONFIG_H + printf( "%s\n", (char *)VERSION ); +#else + printf( "0.0.1\n" ); +#endif + + exit( 1 ); +} + +static void +error( char *s ) +{ + fprintf( stderr, "ERROR: %s: ", progname ); + fprintf( stderr, "%s\n", s ); + exit( 1 ); +} + +static int +is_alpha_or_num( int c ) +{ + return( (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' || c > 126 ); +} + +static int a; +static int b; +static int lookahead = EOF; +static int x = EOF; +static int y = EOF; + +/* + get - return the next character from stdin. Watch out for lookahead. If + the character is a control character, translate it to a space or + linefeed. + */ +static int +get() +{ + int c = lookahead; + lookahead = EOF; + if( c == EOF ) + { + c = getc( ifile ); + } + if( c >= ' ' || c == '\n' || c == EOF ) + { + return c; + } + if( c == '\r' ) + { + return '\n'; + } + return ' '; +} + + +/* + peek - get the next character without getting it. + */ +static int +peek() +{ + lookahead = get(); + return lookahead; +} + + +/* + next - get the next character, excluding comments. peek() is used to see + if a '/' is followed by a '/' or '*'. + */ +static int +next() +{ + int c = get(); + if ( c == '/' ) + { + switch( peek() ) + { + case '/': + for( ;; ) + { + c = get(); + if( c <= '\n' ) + { + break; + } + } + break; + case '*': + get(); + while( c != ' ' ) + { + switch( get() ) + { + case '*': + if( peek() == '/' ) + { + get(); + c = ' '; + } + break; + case EOF: + error( "Unterminated comment" ); + } + } + break; + } + } + y = x; + x = c; + return c; +} + + +/* + action - do something! What you do is determined by the argument: + 1 Output A. Copy B to A. Get the next B. + 2 Copy B to A. Get the next B. (Delete A). + 3 Get the next B. (Delete B). + action treats a string as a single character. Wow! + action recognizes a regular expression if it is preceded by ( or , or =. + */ +static void +action( int d ) +{ + switch( d ) + { + case 1: + putc( a, ofile ); + if( (y == '\n' || y == ' ') && + (a == '+' || a == '-' || a == '*' || a == '/') && + (b == '+' || b == '-' || b == '*' || b == '/') ) + { + putc( y, ofile ); + } + case 2: + a = b; + if( a == '\'' || a == '"' || a == '`' ) + { + for( ;; ) + { + putc( a, ofile ); + a = get(); + if( a == b ) + { + break; + } + if( a == '\\' ) + { + putc( a, ofile ); + a = get(); + } + if( a == EOF ) + { + error( "Unterminated string literal" ); + } + } + } + case 3: + b = next(); + if( b == '/' && + ( a == '(' || a == ',' || a == '=' || a == ':' || + a == '[' || a == '!' || a == '&' || a == '|' || + a == '?' || a == '+' || a == '-' || a == '~' || + a == '*' || a == '/' || a == '{' || a == '\n' ) ) + { + putc( a, ofile ); + if( a == '/' || a == '*' ) + { + putc( ' ', ofile ); + } + putc( b, ofile ); + for( ;; ) + { + a = get(); + if( a == '[' ) + { + for( ;; ) + { + putc( a, ofile ); + a = get(); + if( a == ']' ) + { + break; + } + if( a == '\\' ) + { + putc( a, ofile ); + a = get(); + } + if( a == EOF ) + { + error( "Unterminated set in Regular Expression literal" ); + } + } + } + else if( a == '/' ) + { + switch( peek() ) + { + case '/': + case '*': + error( "Unterminated set in Regular Expression literal" ); + } + break; + } + else if( a =='\\' ) + { + putc( a, ofile ); + a = get(); + } + if( a == EOF ) + { + error( "Unterminated Regular Expression literal" ); + } + putc( a, ofile ); + } + b = next(); + } + } +} + + +/* + jsmin - Copy the input to the output, deleting the characters which are + insignificant to JavaScript. Comments will be removed. Tabs will be + replaced with spaces. Carriage returns will be replaced with linefeeds. + Most spaces and linefeeds will be removed. +*/ + +static void +jsmin() +{ + if( peek() == 0xEF ) { get(); get(); get(); } + a = '\n'; + action( 3 ); + + while( a != EOF ) + { + switch( a ) + { + case ' ': + action(is_alpha_or_num(b) ? 1 : 2); + break; + case '\n': + switch( b ) + { + case '{': case '[': case '(': + case '+': case '-': case '!': + case '~': + action( 1 ); + break; + case ' ': + action( 3 ); + break; + default: + action( is_alpha_or_num(b) ? 1 : 2 ); + } + break; + default: + switch( b ) + { + case ' ': + action( is_alpha_or_num(a) ? 1 : 3 ); + break; + case '\n': + switch( a ) + { + case '}': case ']': case ')': + case '+': case '-': case '"': + case '\'': case '`': + action( 1 ); + break; + default: + action( is_alpha_or_num(a) ? 1 : 3 ); + } + break; + default: + action( 1 ); + break; + } + } + } + /* lats carriage return */ + putc( '\n', ofile ); +} + +int is_file_exist( const char *filename ) +{ + struct stat st; + int result = stat( filename, &st ); + return result == 0; +} + +static void +getargs( argc, argv ) + int argc; + char *argv[]; +{ + int option = 0; + int option_index = 0; + static struct option long_options[] = + { + { "output", required_argument, 0, 'o' }, + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'v' }, + { 0, 0, 0, 0 }, + }; + + opterr = 0; + + while( (option = getopt_long( argc, argv, "o:hv", long_options, &option_index )) != -1 ) + { + switch( option ) + { + case 'o': + ofname = optarg; + break; + case 'h': + opt_usage = 1; + break; + case 'v': + opt_version = 1; + break; + case '?': + { + if( optopt == 'o' ) + fprintf( stderr,"\nERROR: %s: option '-%c' requires an argument\n\n", progname, optopt ); + } + default: + usage(); + break; + } + } + + if( optind < argc ) + { + ifname = argv[optind++]; + if( optind < argc ) usage(); + } + + if( opt_usage ) usage(); + if( opt_version ) version(); + if( opt_usage || ( ! ifname && ! ofname ) ) usage(); + + if( ! ofname ) ofname = "-" ; + if( ! ifname ) ifname = "-" ; +} + +int main( int argc, char *argv[] ) +{ + int use_stdin = 0, use_stdout = 0, use_tmpfile = 0; + + progname = rindex( argv[0], DIR_SEPARATOR ) + 1; + getargs( argc, argv ); + + + if( ! strncmp( ifname, "-", 1 ) ) + { + ifile = stdin; + use_stdin = 1; + } + else + { + ifile = fopen( ifname, "r" ); + if( ifile == NULL ) + { + fprintf( stderr, "ERROR: Can't open '%s' file\n", ifname ); + exit( 1 ); + } + } + + if( ! strncmp( ofname, "-", 1 ) ) + { + ofile = stdout; + use_stdout = 1; + } + else + { + if( is_file_exist( ofname ) ) + { + /* + use temporary file to presave file content + in case when input and output the same file. + */ + ofile = tmpfile(); + if( ofile == NULL ) + { + fprintf( stderr, "ERROR: Can't open TEMPORARY file\n" ); + exit( 1 ); + } + use_tmpfile = 1; + } + else + { + ofile = fopen( ofname, "w+" ); + if( ofile == NULL ) + { + fprintf( stderr, "ERROR: Can't open '%s' file\n", ofname ); + exit( 1 ); + } + } + } + + + jsmin(); + + + if( use_tmpfile ) + { + tmp = fopen( ofname, "w+" ); + if( tmp == NULL ) + { + fprintf( stderr, "ERROR: Can't open '%s' file\n", ofname ); + exit( 1 ); + } + if( !fseek( ofile, 0, SEEK_SET ) ) + { + int c; + while( (c = getc( ofile )) != EOF ) + { + putc( c, tmp ); + } + fclose( tmp ); + } + else + { + fprintf( stderr, "ERROR: Can't seek to beginning of the '%s' file\n", ofname ); + fclose( tmp ); + exit( 1 ); + } + } + + if( ! use_stdin ) { fclose( ifile ); ifile = NULL; } + if( ! use_stdout ) { fclose( ofile ); ofile = NULL; } + exit( 0 ); +} |