diff options
-rw-r--r-- | tools/shmin.sh | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/tools/shmin.sh b/tools/shmin.sh new file mode 100644 index 0000000..9bf50b7 --- /dev/null +++ b/tools/shmin.sh @@ -0,0 +1,257 @@ +#!/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 <http://www.gnu.org/licenses/>. + +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 ! mv "${output}~" "${output}"; then + die 'Cannot rename file to "%s"' "${output}" + fi +} + +usage() +{ + printf 'Usage: %s [option ...] <file>\n' "${0}" +} + +help() +{ + usage + cat <<EOF +Options: + -h Display this information + -V Display minifier version information + -o <output> Write the minified output to <output>, instead of replacing the + existing file +EOF +} + +version() +{ + cat <<EOF +shmin ${VERSION} +Copyright (C) 2015 Patrick "P. J." McDermott +License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +EOF +} + +main() +{ + local input= + + while getopts 'hVo:' opt; do + case "${opt}" in + 'h') + help + exit + ;; + 'V') + version + exit + ;; + 'o') + output="${OPTARG}" + ;; + esac + done + shift $(($OPTIND - 1)) + + if [ ${#} -ne 1 ]; then + usage >&2 + exit 1 + fi + input="${1}" + + if [ "x${output}" = 'x' ]; then + output="${input}" + fi + + minify "${input}" +} + +main "${@}" |