From 2fee06d9f447ea100e79eb2be57729b769048d19 Mon Sep 17 00:00:00 2001
From: P. J. McDermott <pj@pehjota.net>
Date: Mon, 26 Oct 2015 23:43:43 -0400
Subject: tools/shmin.sh: New file

---
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 "${@}"
--
cgit v0.9.1