#!/bin/sh
#
# Shell command language minifier
#
# Copyright (C) 2015 Patrick "P. J." McDermott
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
set -u
VERSION='0.1.0'
HT=' '
LF='
'
c=
output=''
die()
{
local fmt="${1}"
shift 1
printf "shmin: ${fmt}\n" "${@}"
exit 2
}
fgetc()
{
# Damn command substitutions eat trailing whitespace. Even if
# whitespace is the /only/ thing printed and exactly what we want from
# the command.
c="$(dd bs=1 count=1 <&3 2>/dev/null; printf '.')"
c="${c%.}"
}
minify()
{
local input="${1}"
local buffer=''
local escaped=false
# Open input file.
if ! exec 3<"${input}"; then
die 'Cannot open file "%s"' "${input}"
fi
# Check for magic number or other first-line comment.
fgetc
if [ "x${c}" = 'x#' ]; then
# Comment
fgetc
if [ "x${c}" = 'x!' ]; then
# Magic number
buffer="${buffer}#!"
while :; do
fgetc
buffer="${buffer}${c}"
if [ "x${c}" = "x${LF}" ]; then
break
fi
done
else
while :; do
fgetc
if [ "x${c}" = "x${LF}" ]; then
break
fi
done
fi
fi
# Strip leading whitespace on the first line.
while :; do
case "${c}" in
' ' | "${HT}" | "${LF}")
;;
*)
break
;;
esac
fgetc
done
# Parse the rest of the file.
# Like any good parser, this is some very ugly and fragile code.
# Whitespace code must come before comment code must come before newline
# code must come before everything else.
while :; do
if [ "x${c}" = 'x' ]; then
break
fi
if [ "x${c}" = 'x ' ] || [ "x${c}" = "x${HT}" ]; then
# Whitespace
while :; do
fgetc
case "${c}" in
' ' | "${HT}")
;;
"${LF}" | '#')
# Eat trailing whitespace.
break
;;
*)
# Condense whitespace.
buffer="${buffer} "
break
;;
esac
done
fi
if [ "x${c}" = 'x#' ]; then
# Comment
while :; do
fgetc
if [ "x${c}" = "x${LF}" ]; then
break
fi
done
fi
if [ "x${c}" = "x${LF}" ]; then
# Strip empty lines and leading whitespace.
while :; do
fgetc
case "${c}" in
' ' | "${HT}" | "${LF}")
;;
*)
buffer="${buffer}${LF}"
break
;;
esac
done
fi
if [ "x${c}" = "x'" ]; then
# Single quotes
buffer="${buffer}${c}"
while :; do
fgetc
buffer="${buffer}${c}"
if [ "x${c}" = "x'" ]; then
break
fi
done
fgetc
continue
fi
if [ "x${c}" = 'x"' ]; then
# Double quotes
buffer="${buffer}${c}"
while :; do
fgetc
buffer="${buffer}${c}"
if ${escaped}; then
escaped=false
continue
fi
if [ "x${c}" = 'x\' ]; then
escaped=true
continue
fi
if [ "x${c}" = 'x"' ]; then
break
fi
done
fgetc
continue
fi
buffer="${buffer}${c}"
fgetc
done
# We made it. Now write the output.
if ! printf '%s' "${buffer}" >"${output}~"; then
die 'Cannot write file "%s"' "${output}~"
fi
# Set output file name.
if ! cat "${output}~" >"${output}"; then
die 'Cannot rename file to "%s"' "${output}"
fi
if ! rm "${output}~"; then
die 'Cannot remove file "%s"' "${output}~"
fi
}
usage()
{
printf 'Usage: %s [option ...] \n' "${0}"
}
help()
{
usage
cat < Write the minified output to