#!/usr/bin/perl -w use strict; use warnings; # # This perl script converts a JavaScript file to a minimial size. # Written by Zsolt Nagy-Perge in January 2021, Pensacola, Fla. # # About(); @ARGV or die "\n Usage: perl minify.pl \n\n"; my $START = time; my $OUTPUT = ''; my $JSFILE = shift(@ARGV); isFromCharSet($JSFILE, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._/\\!: ') or die "\nOops. File name contains illegal characters - $JSFILE\n"; my $MYPATH = GetCurrentDirectory(); my $DATA = ReadFile($JSFILE); length($DATA) or die "\nThis file either doesn't exist or has a size of zero - $JSFILE\n"; my $LEN = -s $JSFILE; print "\n --> Read ", Commify($LEN), " bytes.\n\n"; MinifyJS(); my $NEW_FILE = cut($JSFILE, '.js', 0x01110) . '_min.js'; CreateFile($NEW_FILE, $OUTPUT); my $TDIFF = time - $START; print ' --> Written ', Commify(length($OUTPUT)), ' bytes to ', $NEW_FILE, " in $TDIFF secs.\n"; exit; ################################################## sub MinifyJS { my $WORD = 0; # Are we reading a word? my $NUMBER = 0; # Are we reading a number? my $STRING = 0; # Are we reading a string? my $COMMENT = 0; # Are we reading a comment? my ($c, $i, $p) = (0); my $n; for ($i = 0; $i <= length($DATA); $i++) { $p = $c; $c = vec($DATA, $i, 8); $n = vec($DATA, $i+1, 8); if ($COMMENT == 1) # Are we in a one-line comment? { if ($c == 10 || $c == 13) # Wait until end of line { $COMMENT = 0; } next; } if ($COMMENT == 2) # Are we in a comment block? { if ($p == 42 && $c == 47) # Wait for end marker: */ { $COMMENT = 0; } next; } if ($STRING) # Are we in a string? { OUT($c); if ($c == 92 && $p == 92) # This counts as one backslash { $c = 0; next; } if ($c == $STRING && $p != 92) # Wait for closing quote { $STRING = 0; } next; } # Are we at the beginning of a string? if (($c == 34 || $c == 39) && $p != 92) { OUT($c); $STRING = $c; next; } # RegExp probably: if ($c == 47 && $p == 92) { $COMMENT = 0; OUT($c); $c = 0; next; } # One-line comment begins: if ($c == 47 && $p == 47) { $COMMENT = 1; BKSP(); next; } # Comment block begins: if ($c == 42 && $p == 47) { $COMMENT = 2; BKSP(); next; } # Are we reading a number? if ($NUMBER) { if ($p == 48 && $c == 120) # Hexadecimal number? { OUT($c); next; # Just keep going } elsif (!isDigit($c)) { $NUMBER = 0; } } elsif (isDigit($c) && !isDigit($p)) { $NUMBER = 1; OUT($c); next; } # Are we reading a word? if ($WORD) { if (isWordChar($c)) { OUT($c); next; } else # End of word? { # SaveName(DATA.substring(WORD - 1, i)); $WORD = 0; } } elsif (isWordInitial($c)) { $WORD = $i + 1; OUT($c); next; } # Whitespace: if ($c < 33) { if (isWordChar($p) && isWordChar($n)) { OUT($c); } next; } else { OUT($c); } } } ################################################## sub BKSP { if (length($OUTPUT)) { $OUTPUT = substr($OUTPUT, 0, length($OUTPUT) - 1); } } ################################################## # v2021.1.20 # Writes one byte to the output buffer. # Usage: OUT(INTEGER) # sub OUT { my $c = defined $_[0] ? $_[0] : 0; $OUTPUT .= chr($c); } ################################################## # Returns 1 if a character is a digit. # Usage: INTEGER = isDigit(INTEGER) # sub isDigit { my $c = defined $_[0] ? $_[0] : 0; return ($c > 47 && $c < 58) ? 1 : 0; } ################################################## # Returns 1 if a character might be the first # letter of a variable or function name or keyword. # Usage: isWordInitial(c) # sub isWordInitial { my $c = defined $_[0] ? $_[0] : 0; return 1 if ($c == 36 || $c == 95); return 1 if ($c > 64 && $c < 91); return 1 if ($c > 96 && $c < 123); return 0; } ################################################## # Returns 1 if a character might be part # of a variable or function name or keyword. # Usage: INTEGER = isWordChar(INTEGER) # sub isWordChar { my $c = defined $_[0] ? $_[0] : 0; return 1 if ($c == 36 || $c == 95); return 1 if ($c > 64 && $c < 91); return 1 if ($c > 47 && $c < 58); return 1 if ($c > 96 && $c < 123); return 0; } ################################################## # v2021.1.20 # Returns the Nth command-line argument or DEFAULT # if not specified. When N is zero, the name of # this script is returned. When N is 1, the first # command-line argument is returned, and so forth. # Usage: STRING = CmdArgStr(N, DEFAULT) # sub CmdArgStr { @_ or return ''; my $N = defined $_[0] ? $_[0] : 0; $N-- or return $0; my $DEFAULT = defined $_[1] ? $_[1] : ''; return @ARGV > $N ? $ARGV[$N] : $DEFAULT; } ################################################## # v2021.1.20 # Returns the current working directory. # Usage: STRING = GetCurrentDirectory() # sub GetCurrentDirectory { my $PATH = Trim(exists($ENV{PWD}) ? $ENV{PWD} : `cd`); $PATH =~ tr|\\|/|; if (vec($PATH, length($PATH) - 1, 8) != 47) { $PATH .= '/'; } return $PATH; } # Cuts a string into two parts and returns the second half if MASK is 1. Returns first half if MASK is 0x10. If the match isn't found, still returns the whole string if MASK is 0x100. Starts searching from the end of string if MASK is 0x1000. Ignore case when MASK is 0x10000. # Usage: STRING = cut(STRING, SUBSTR, MASK) sub cut { my $OUTPUT = ''; my $STR = defined $_[0] ? $_[0] : ''; my $SUB = defined $_[1] ? $_[1] : ''; my $MASK = defined $_[2] ? $_[2] : 0x111; if ($MASK & 0x10000) { $STR = uc($STR); $SUB = uc($SUB); } my $P = ($MASK & 0x1000) ? rindex($STR, $SUB) : index($STR, $SUB); $P >= 0 or return ($MASK & 256) ? $STR : ''; if ($MASK & 16) { $OUTPUT = substr($STR, 0, $P); } if ($MASK & 1) { $OUTPUT .= substr($STR, $P + length($SUB)); } return $OUTPUT; } # Removes whitespace before and after STRING. # Usage: STRING = Trim(STRING) sub Trim { defined $_[0] or return ''; (my $L = length($_[0])) or return ''; my $P = 0; while ($P <= $L && vec($_[0], $P++, 8) < 33) {} for ($P--; $P <= $L && vec($_[0], $L--, 8) < 33;) {} substr($_[0], $P, $L - $P + 2); } ################################################## # # This function reads the entire contents of a file # in binary mode and returns it as a string. If an # errors occur, an empty string is returned silently. # A second argument will move the file pointer before # reading. And a third argument limits the number # of bytes to read. # Usage: STRING = ReadFile(FILENAME, [START, [LENGTH]]) # sub ReadFile { my $NAME = defined $_[0] ? $_[0] : ''; $NAME =~ tr/\"\0*?|<>//d; # Remove special characters -e $NAME or return ''; -f $NAME or return ''; my $SIZE = -s $NAME; $SIZE or return ''; my $LEN = defined $_[2] ? $_[2] : $SIZE; $LEN > 0 or return ''; local *FH; sysopen(FH, $NAME, 0) or return ''; binmode FH; my $POS = defined $_[1] ? $_[1] : 0; $POS < $SIZE or return ''; $POS < 1 or sysseek(FH, 0, $POS); # Move file ptr my $DATA = ''; sysread(FH, $DATA, $LEN); # Read file close FH; return $DATA; } ################################################## # v2019.11.24 # Creates and overwrites a file in binary mode. # Returns 1 on success or 0 if something went wrong. # Usage: INTEGER = CreateFile(FILE_NAME, CONTENT) # sub CreateFile { defined $_[0] or return 0; my $F = $_[0]; $F =~ tr/\"\0*?|<>//d; # Remove special characters length($F) or return 0; local *FH; open(FH, ">$F") or return 0; binmode FH; if (defined $_[1] ? length($_[1]) : 0) { print FH $_[1]; } close FH or return 0; return 1; } ################################################## # This function returns 1 if string S is strictly made up of characters listed in string KNOWN. Returns 0 if string S contains any "unknown" characters. # Usage: INTEGER = isFromCharSet(STRING, KNOWNSET) sub isFromCharSet { defined $_[0] or return -1; defined $_[1] or return 0; length($_[1]) or return 0; (my $L = length($_[0])) or return -1; while ($L--) { index($_[1], substr($_[0], $L, 1)) >= 0 or return 0; } return 1; } ################################################## # # This function inserts commas into a number at # every 3 digits and returns a string. # Usage: STRING = Commify(INTEGER) # Copied from www.PerlMonks.org/?node_id=157725 # sub Commify { my $N = reverse $_[0]; $N =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g; return scalar reverse $N; } ################################################## sub About { print "\n ", '_' x 77; print "\n JS FILE MINIFY, written by Zsolt Nagy-Perge in Jan 2021"; print "\n ", '~' x 77, "\n"; }