1ab2043b8SDevin Teskeif [ ! "$_STRINGS_SUBR" ]; then _STRINGS_SUBR=1
2ab2043b8SDevin Teske#
30cb8bea7SDevin Teske# Copyright (c) 2006-2016 Devin Teske
4f8ea072aSDevin Teske# All rights reserved.
5ab2043b8SDevin Teske#
6ab2043b8SDevin Teske# Redistribution and use in source and binary forms, with or without
7ab2043b8SDevin Teske# modification, are permitted provided that the following conditions
8ab2043b8SDevin Teske# are met:
9ab2043b8SDevin Teske# 1. Redistributions of source code must retain the above copyright
10ab2043b8SDevin Teske#    notice, this list of conditions and the following disclaimer.
11ab2043b8SDevin Teske# 2. Redistributions in binary form must reproduce the above copyright
12ab2043b8SDevin Teske#    notice, this list of conditions and the following disclaimer in the
13ab2043b8SDevin Teske#    documentation and/or other materials provided with the distribution.
14ab2043b8SDevin Teske#
15ab2043b8SDevin Teske# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
168e37a7c8SDevin Teske# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17ab2043b8SDevin Teske# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18ab2043b8SDevin Teske# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19ab2043b8SDevin Teske# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
208e37a7c8SDevin Teske# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21ab2043b8SDevin Teske# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22ab2043b8SDevin Teske# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23ab2043b8SDevin Teske# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24ab2043b8SDevin Teske# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25ab2043b8SDevin Teske# SUCH DAMAGE.
26ab2043b8SDevin Teske#
27d3a0f918SDevin Teske#
28fcaed0c1SDevin Teske############################################################ INCLUDES
29fcaed0c1SDevin Teske
30fcaed0c1SDevin TeskeBSDCFG_SHARE="/usr/share/bsdconfig"
31fcaed0c1SDevin Teske. $BSDCFG_SHARE/common.subr || exit 1
32fcaed0c1SDevin Teske
33d3a0f918SDevin Teske############################################################ GLOBALS
34d3a0f918SDevin Teske
35d3a0f918SDevin Teske#
3605a0a04aSDevin Teske# A Literal newline (for use with f_replace_all(), or IFS, or whatever)
3705a0a04aSDevin Teske#
3805a0a04aSDevin TeskeNL="
3905a0a04aSDevin Teske" # END-QUOTE
4005a0a04aSDevin Teske
4105a0a04aSDevin Teske#
42d3a0f918SDevin Teske# Valid characters that can appear in an sh(1) variable name
43d3a0f918SDevin Teske#
44d3a0f918SDevin Teske# Please note that the character ranges A-Z and a-z should be avoided because
45d3a0f918SDevin Teske# these can include accent characters (which are not valid in a variable name).
46d3a0f918SDevin Teske# For example, A-Z matches any character that sorts after A but before Z,
47d3a0f918SDevin Teske# including A and Z. Although ASCII order would make more sense, that is not
48d3a0f918SDevin Teske# how it works.
49d3a0f918SDevin Teske#
50d3a0f918SDevin TeskeVALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
51d3a0f918SDevin Teske
52d3a0f918SDevin Teske############################################################ FUNCTIONS
53ab2043b8SDevin Teske
5492db3842SDevin Teske# f_isinteger $arg
5592db3842SDevin Teske#
5692db3842SDevin Teske# Returns true if argument is a positive/negative whole integer.
5792db3842SDevin Teske#
5892db3842SDevin Teskef_isinteger()
5992db3842SDevin Teske{
6092db3842SDevin Teske	local arg="${1#-}"
6192db3842SDevin Teske	[ "${arg:-x}" = "${arg%[!0-9]*}" ]
6292db3842SDevin Teske}
6392db3842SDevin Teske
640cb8bea7SDevin Teske# f_substr [-v $var_to_set] $string $start [$length]
65ab2043b8SDevin Teske#
660cb8bea7SDevin Teske# Similar to awk(1)'s substr(), return length substring of string that begins
670cb8bea7SDevin Teske# at start position counted from 1.
68ab2043b8SDevin Teske#
69437455deSDevin Teskecase "$BASH_VERSION" in
70437455deSDevin Teske*?*)
71437455deSDevin Teske	f_substr()
72437455deSDevin Teske	{
73437455deSDevin Teske		local __var_to_set=
74437455deSDevin Teske		case "$1" in
75437455deSDevin Teske		-v) __var_to_set="$2"; shift 2 ;;
76437455deSDevin Teske		-v?*) __var_to_set="${2#-v}"; shift 1 ;;
77437455deSDevin Teske		esac
78437455deSDevin Teske		local __tmp="$1" __start="${2:-1}"  __len="$3"
79437455deSDevin Teske		[ "$__start" -gt 0 ] 2> /dev/null &&
80437455deSDevin Teske			__start=$(( $__start - 1 ))
81437455deSDevin Teske		if [ ! "$__var_to_set" ]; then
82437455deSDevin Teske			eval echo \"\${__tmp:\$__start${__len:+:\$__len}}\"
83437455deSDevin Teske			return $?
84437455deSDevin Teske		fi
85437455deSDevin Teske		if [ "$__len" ]; then
86437455deSDevin Teske			eval $__var_to_set=\"\${__tmp:\$__start:\$__len}\"
87437455deSDevin Teske		else
88437455deSDevin Teske			eval $__var_to_set=\"\${__tmp:\$__start}\"
89437455deSDevin Teske		fi
90437455deSDevin Teske	}
91437455deSDevin Teske	;;
92437455deSDevin Teske*)
93437455deSDevin Teske	# NB: On FreeBSD, sh(1) runs this faster than bash(1) runs the above
94ab2043b8SDevin Teske	f_substr()
95ab2043b8SDevin Teske	{
960cb8bea7SDevin Teske		local OPTIND=1 OPTARG __flag __var_to_set=
970cb8bea7SDevin Teske		while getopts v: __flag; do
980cb8bea7SDevin Teske			case "$__flag" in
990cb8bea7SDevin Teske			v) __var_to_set="$OPTARG" ;;
1000cb8bea7SDevin Teske			esac
1010cb8bea7SDevin Teske		done
1020cb8bea7SDevin Teske		shift $(( $OPTIND - 1 ))
103ab2043b8SDevin Teske
1040cb8bea7SDevin Teske		local __tmp="$1" __start="${2:-1}" __size="$3"
1050cb8bea7SDevin Teske		local __tbuf __tbuf_len __trim __trimq
1060f913ed5SDevin Teske
1070cb8bea7SDevin Teske		if [ ! "$__tmp" ]; then
1080cb8bea7SDevin Teske			[ "$__var_to_set" ] && setvar "$__var_to_set" ""
1090f913ed5SDevin Teske			return ${SUCCESS:-0}
1100cb8bea7SDevin Teske		fi
1110cb8bea7SDevin Teske		[ "$__start" -ge 1 ] 2> /dev/null || __start=1
1120cb8bea7SDevin Teske		if ! [ "${__size:-1}" -ge 1 ] 2> /dev/null; then
1130cb8bea7SDevin Teske			[ "$__var_to_set" ] && setvar "$__var_to_set" ""
1140f913ed5SDevin Teske			return ${FAILURE:-1}
1150f913ed5SDevin Teske		fi
1160f913ed5SDevin Teske
1170cb8bea7SDevin Teske		__trim=$(( $__start - 1 ))
1180cb8bea7SDevin Teske		while [ $__trim -gt 0 ]; do
1190cb8bea7SDevin Teske			__tbuf="?"
1200cb8bea7SDevin Teske			__tbuf_len=1
121437455deSDevin Teske			while [ $__tbuf_len -lt $(( $__trim / $__tbuf_len )) ]
122437455deSDevin Teske			do
1230cb8bea7SDevin Teske				__tbuf="$__tbuf?"
1240cb8bea7SDevin Teske				__tbuf_len=$(( $__tbuf_len + 1 ))
1250cb8bea7SDevin Teske			done
1260cb8bea7SDevin Teske			__trimq=$(( $__trim / $__tbuf_len ))
1270cb8bea7SDevin Teske			__trim=$(( $__trim - $__tbuf_len * $__trimq ))
1280cb8bea7SDevin Teske			while [ $__trimq -gt 0 ]; do
1290cb8bea7SDevin Teske				__tmp="${__tmp#$__tbuf}"
1300cb8bea7SDevin Teske				__trimq=$(( $__trimq - 1 ))
1310cb8bea7SDevin Teske			done
1320cb8bea7SDevin Teske		done
1330f913ed5SDevin Teske
1340cb8bea7SDevin Teske		local __tmp_size=${#__tmp}
1350f913ed5SDevin Teske		local __mask __mask_len
1360cb8bea7SDevin Teske		__trim=$(( $__tmp_size - ${__size:-$__tmp_size} ))
1370f913ed5SDevin Teske		while [ $__trim -gt 0 ]; do
1380f913ed5SDevin Teske			__tbuf="?"
1390f913ed5SDevin Teske			__tbuf_len=1
1400f913ed5SDevin Teske			if [ $__trim -le $__size ]; then
141437455deSDevin Teske				while [ $__tbuf_len -lt $((
142437455deSDevin Teske					$__trim / $__tbuf_len
143437455deSDevin Teske				)) ]; do
1440f913ed5SDevin Teske					__tbuf="$__tbuf?"
1450f913ed5SDevin Teske					__tbuf_len=$(( $__tbuf_len + 1 ))
1460f913ed5SDevin Teske				done
1470f913ed5SDevin Teske				__trimq=$(( $__trim / $__tbuf_len ))
1480f913ed5SDevin Teske				__trim=$(( $__trim - $__tbuf_len * $__trimq ))
1490f913ed5SDevin Teske				while [ $__trimq -gt 0 ]; do
1500cb8bea7SDevin Teske					__tmp="${__tmp%$__tbuf}"
1510f913ed5SDevin Teske					__trimq=$(( $__trimq - 1 ))
1520f913ed5SDevin Teske				done
1530f913ed5SDevin Teske			else
1540cb8bea7SDevin Teske				__mask="$__tmp"
155437455deSDevin Teske				while [ $__tbuf_len -lt $((
156437455deSDevin Teske					$__size / $__tbuf_len
157437455deSDevin Teske				)) ]; do
1580f913ed5SDevin Teske					__tbuf="$__tbuf?"
1590f913ed5SDevin Teske					__tbuf_len=$(( $__tbuf_len + 1 ))
1600f913ed5SDevin Teske				done
1610f913ed5SDevin Teske				__trimq=$(( $__size / $__tbuf_len ))
162437455deSDevin Teske				if [ $__size -ne $((
163437455deSDevin Teske					$__trimq * $__tbuf_len
164437455deSDevin Teske				)) ]; then
1650f913ed5SDevin Teske					__tbuf="$__tbuf?"
1660f913ed5SDevin Teske					__tbuf_len=$(( $__tbuf_len + 1 ))
1670f913ed5SDevin Teske				fi
168437455deSDevin Teske				__mask_len=$((
169437455deSDevin Teske					$__tmp_size - $__tbuf_len * $__trimq
170437455deSDevin Teske				))
171437455deSDevin Teske				__trim=$((
172437455deSDevin Teske					$__tmp_size - $__mask_len - $__size
173437455deSDevin Teske				))
1740f913ed5SDevin Teske				while [ $__trimq -gt 0 ]; do
1750f913ed5SDevin Teske					__mask="${__mask#$__tbuf}"
1760f913ed5SDevin Teske					__trimq=$(( $__trimq - 1 ))
1770f913ed5SDevin Teske				done
1780cb8bea7SDevin Teske				__tmp="${__tmp%"$__mask"}"
1790f913ed5SDevin Teske			fi
1800f913ed5SDevin Teske		done
1810cb8bea7SDevin Teske
1829e81c4fbSDevin Teske		if [ "$__var_to_set" ]; then
1830cb8bea7SDevin Teske			setvar "$__var_to_set" "$__tmp"
1849e81c4fbSDevin Teske		else
1859e81c4fbSDevin Teske			echo "$__tmp"
1869e81c4fbSDevin Teske		fi
1870cb8bea7SDevin Teske	}
188437455deSDevin Teskeesac
1890cb8bea7SDevin Teske
1900efb8b7aSDevin Teske# f_sprintf $var_to_set $format [$arguments ...]
1910efb8b7aSDevin Teske#
1920efb8b7aSDevin Teske# Similar to sprintf(3), write a string into $var_to_set using printf(1) syntax
1930efb8b7aSDevin Teske# (`$format [$arguments ...]').
1940efb8b7aSDevin Teske#
1957883f920SDevin Teskecase "$BASH_VERSION" in
1967883f920SDevin Teske3.1*|4.*)
1977883f920SDevin Teske	f_sprintf()
1987883f920SDevin Teske	{
1997883f920SDevin Teske		local __var_to_set="$1" __tmp
2007883f920SDevin Teske		shift 1 # var_to_set
2017883f920SDevin Teske		printf -v __tmp "$@"
2027883f920SDevin Teske		eval "$__var_to_set"=\"\${__tmp%\$NL}\"
2037883f920SDevin Teske	}
2047883f920SDevin Teske	;;
2057883f920SDevin Teske*)
2067883f920SDevin Teske	# NB: On FreeBSD, sh(1) runs this faster than bash(1) runs the above
2070efb8b7aSDevin Teske	f_sprintf()
2080efb8b7aSDevin Teske	{
2090efb8b7aSDevin Teske		local __var_to_set="$1"
2100efb8b7aSDevin Teske		shift 1 # var_to_set
2117883f920SDevin Teske		eval "$__var_to_set"=\$\( printf -- \"\$@\" \)
2120efb8b7aSDevin Teske	}
2137883f920SDevin Teskeesac
2140efb8b7aSDevin Teske
215e4f08d49SDevin Teske# f_vsprintf $var_to_set $format $format_args
216e4f08d49SDevin Teske#
217e4f08d49SDevin Teske# Similar to vsprintf(3), write a string into $var_to_set using printf(1)
218e4f08d49SDevin Teske# syntax (`$format $format_args').
219e4f08d49SDevin Teske#
220e4f08d49SDevin Teskef_vsprintf()
221e4f08d49SDevin Teske{
222e4f08d49SDevin Teske	eval f_sprintf \"\$1\" \"\$2\" $3
223e4f08d49SDevin Teske}
224e4f08d49SDevin Teske
2253c8cd13bSDevin Teske# f_snprintf $var_to_set $size $format [$arguments ...]
2263c8cd13bSDevin Teske#
2273c8cd13bSDevin Teske# Similar to snprintf(3), write at most $size number of bytes into $var_to_set
2283c8cd13bSDevin Teske# using printf(1) syntax (`$format [$arguments ...]').
2293c8cd13bSDevin Teske#
2303c8cd13bSDevin Teskef_snprintf()
2313c8cd13bSDevin Teske{
2323c8cd13bSDevin Teske	local __var_to_set="$1" __size="$2"
2333c8cd13bSDevin Teske	shift 2 # var_to_set size
2343c8cd13bSDevin Teske
2353c8cd13bSDevin Teske	local __f_snprintf_tmp
2363c8cd13bSDevin Teske	f_sprintf __f_snprintf_tmp "$@"
2373c8cd13bSDevin Teske	f_substr "$__var_to_set" "$__f_snprintf_tmp" 1 "$__size"
2383c8cd13bSDevin Teske}
2393c8cd13bSDevin Teske
240dd5cc066SDevin Teske# f_vsnprintf $var_to_set $size $format $format_args
241dd5cc066SDevin Teske#
242dd5cc066SDevin Teske# Similar to vsnprintf(3), write at most $size number of bytes into $var_to_set
243dd5cc066SDevin Teske# using printf(1) syntax (`$format $format_args'). The value of $var_to_set is
244dd5cc066SDevin Teske# NULL unless at-least one byte is stored from the output.
245dd5cc066SDevin Teske#
246dd5cc066SDevin Teske# Example 1:
247dd5cc066SDevin Teske#
248dd5cc066SDevin Teske# 	limit=7 format="%s"
249dd5cc066SDevin Teske# 	format_args="'abc   123'" # 3-spaces between abc and 123
250dd5cc066SDevin Teske# 	f_vsnprintf foo $limit "$format" "$format_args" # foo=[abc   1]
251dd5cc066SDevin Teske#
252dd5cc066SDevin Teske# Example 2:
253dd5cc066SDevin Teske#
254dd5cc066SDevin Teske# 	limit=12 format="%s %s"
25533291485SDevin Teske# 	format_args="   'doghouse'      'fox'   "
256dd5cc066SDevin Teske# 		# even more spaces added to illustrate escape-method
257dd5cc066SDevin Teske# 	f_vsnprintf foo $limit "$format" "$format_args" # foo=[doghouse fox]
258dd5cc066SDevin Teske#
259dd5cc066SDevin Teske# Example 3:
260dd5cc066SDevin Teske#
261dd5cc066SDevin Teske# 	limit=13 format="%s %s"
262dd5cc066SDevin Teske# 	f_shell_escape arg1 'aaa"aaa' # arg1=[aaa"aaa] (no change)
263dd5cc066SDevin Teske# 	f_shell_escape arg2 "aaa'aaa" # arg2=[aaa'\''aaa] (escaped s-quote)
264dd5cc066SDevin Teske# 	format_args="'$arg1' '$arg2'" # use single-quotes to surround args
265dd5cc066SDevin Teske# 	f_vsnprintf foo $limit "$format" "$format_args" # foo=[aaa"aaa aaa'a]
266dd5cc066SDevin Teske#
267dd5cc066SDevin Teske# In all of the above examples, the call to f_vsnprintf() does not change. Only
268dd5cc066SDevin Teske# the contents of $limit, $format, and $format_args changes in each example.
269dd5cc066SDevin Teske#
270dd5cc066SDevin Teskef_vsnprintf()
271dd5cc066SDevin Teske{
272dd5cc066SDevin Teske	eval f_snprintf \"\$1\" \"\$2\" \"\$3\" $4
273dd5cc066SDevin Teske}
274dd5cc066SDevin Teske
275d3a0f918SDevin Teske# f_replaceall $string $find $replace [$var_to_set]
276d3a0f918SDevin Teske#
277f82ca17bSDevin Teske# Replace all occurrences of $find in $string with $replace. If $var_to_set is
278d3a0f918SDevin Teske# either missing or NULL, the variable name is produced on standard out for
279d3a0f918SDevin Teske# capturing in a sub-shell (which is less recommended due to performance
280d3a0f918SDevin Teske# degradation).
281d3a0f918SDevin Teske#
28233db33a7SDevin Teske# To replace newlines or a sequence containing the newline character, use $NL
28333db33a7SDevin Teske# as `\n' is not supported.
28433db33a7SDevin Teske#
285d3a0f918SDevin Teskef_replaceall()
286d3a0f918SDevin Teske{
287d3a0f918SDevin Teske	local __left="" __right="$1"
288d3a0f918SDevin Teske	local __find="$2" __replace="$3" __var_to_set="$4"
289d3a0f918SDevin Teske	while :; do
290d3a0f918SDevin Teske		case "$__right" in *$__find*)
291d3a0f918SDevin Teske			__left="$__left${__right%%$__find*}$__replace"
292d3a0f918SDevin Teske			__right="${__right#*$__find}"
293d3a0f918SDevin Teske			continue
294d3a0f918SDevin Teske		esac
295d3a0f918SDevin Teske		break
296d3a0f918SDevin Teske	done
297d3a0f918SDevin Teske	__left="$__left${__right#*$__find}"
298d3a0f918SDevin Teske	if [ "$__var_to_set" ]; then
299d3a0f918SDevin Teske		setvar "$__var_to_set" "$__left"
300d3a0f918SDevin Teske	else
301d3a0f918SDevin Teske		echo "$__left"
302d3a0f918SDevin Teske	fi
303d3a0f918SDevin Teske}
304d3a0f918SDevin Teske
305d3a0f918SDevin Teske# f_str2varname $string [$var_to_set]
306d3a0f918SDevin Teske#
307d3a0f918SDevin Teske# Convert a string into a suitable value to be used as a variable name
308d3a0f918SDevin Teske# by converting unsuitable characters into the underscrore [_]. If $var_to_set
309d3a0f918SDevin Teske# is either missing or NULL, the variable name is produced on standard out for
310d3a0f918SDevin Teske# capturing in a sub-shell (which is less recommended due to performance
311d3a0f918SDevin Teske# degradation).
312d3a0f918SDevin Teske#
313d3a0f918SDevin Teskef_str2varname()
314d3a0f918SDevin Teske{
315d3a0f918SDevin Teske	local __string="$1" __var_to_set="$2"
316d3a0f918SDevin Teske	f_replaceall "$__string" "[!$VALID_VARNAME_CHARS]" "_" "$__var_to_set"
317d3a0f918SDevin Teske}
318d3a0f918SDevin Teske
319d3a0f918SDevin Teske# f_shell_escape $string [$var_to_set]
320d3a0f918SDevin Teske#
321d3a0f918SDevin Teske# Escape $string for shell eval statement(s) by replacing all single-quotes
322d3a0f918SDevin Teske# with a special sequence that creates a compound string when interpolated
323d3a0f918SDevin Teske# by eval with surrounding single-quotes.
324d3a0f918SDevin Teske#
325d3a0f918SDevin Teske# For example:
326d3a0f918SDevin Teske#
327d3a0f918SDevin Teske# 	foo="abc'123"
328d3a0f918SDevin Teske# 	f_shell_escape "$foo" bar # bar=[abc'\''123]
32910908a6fSDevin Teske# 	eval echo \'$bar\' # produces abc'123
330d3a0f918SDevin Teske#
331d3a0f918SDevin Teske# This is helpful when processing an argument list that has to retain its
332d3a0f918SDevin Teske# escaped structure for later evaluations.
333d3a0f918SDevin Teske#
334d3a0f918SDevin Teske# WARNING: Surrounding single-quotes are not added; this is the responsibility
335d3a0f918SDevin Teske# of the code passing the escaped values to eval (which also aids readability).
336d3a0f918SDevin Teske#
337d3a0f918SDevin Teskef_shell_escape()
338d3a0f918SDevin Teske{
339d3a0f918SDevin Teske	local __string="$1" __var_to_set="$2"
340d3a0f918SDevin Teske	f_replaceall "$__string" "'" "'\\''" "$__var_to_set"
341d3a0f918SDevin Teske}
342d3a0f918SDevin Teske
343d3a0f918SDevin Teske# f_shell_unescape $string [$var_to_set]
344d3a0f918SDevin Teske#
345d3a0f918SDevin Teske# The antithesis of f_shell_escape(), this function takes an escaped $string
346d3a0f918SDevin Teske# and expands it.
347d3a0f918SDevin Teske#
348d3a0f918SDevin Teske# For example:
349d3a0f918SDevin Teske#
350d3a0f918SDevin Teske# 	foo="abc'123"
351d3a0f918SDevin Teske# 	f_shell_escape "$foo" bar # bar=[abc'\''123]
352d3a0f918SDevin Teske# 	f_shell_unescape "$bar" # produces abc'123
353d3a0f918SDevin Teske#
354d3a0f918SDevin Teskef_shell_unescape()
355d3a0f918SDevin Teske{
356d3a0f918SDevin Teske	local __string="$1" __var_to_set="$2"
357d3a0f918SDevin Teske	f_replaceall "$__string" "'\\''" "'" "$__var_to_set"
358d3a0f918SDevin Teske}
359d3a0f918SDevin Teske
360a96ea66fSDevin Teske# f_expand_number $string [$var_to_set]
361a96ea66fSDevin Teske#
362a96ea66fSDevin Teske# Unformat $string into a number, optionally to be stored in $var_to_set. This
363a96ea66fSDevin Teske# function follows the SI power of two convention.
364a96ea66fSDevin Teske#
365a96ea66fSDevin Teske# The prefixes are:
366a96ea66fSDevin Teske#
367a96ea66fSDevin Teske# 	Prefix	Description	Multiplier
368a96ea66fSDevin Teske# 	k	kilo		1024
369a96ea66fSDevin Teske# 	M	mega		1048576
370a96ea66fSDevin Teske# 	G	giga		1073741824
371a96ea66fSDevin Teske# 	T	tera		1099511627776
372a96ea66fSDevin Teske# 	P	peta		1125899906842624
373a96ea66fSDevin Teske# 	E	exa		1152921504606846976
374a96ea66fSDevin Teske#
375a96ea66fSDevin Teske# NOTE: Prefixes are case-insensitive.
376a96ea66fSDevin Teske#
3779acbeddcSDevin Teske# Upon successful completion, success status is returned; otherwise the number
3789acbeddcSDevin Teske# -1 is produced ($var_to_set set to -1 or if $var_to_set is NULL or missing)
3799acbeddcSDevin Teske# on standard output. In the case of failure, the error status will be one of:
380a96ea66fSDevin Teske#
3819acbeddcSDevin Teske# 	Status	Reason
3829acbeddcSDevin Teske# 	1	Given $string contains no digits
3839acbeddcSDevin Teske# 	2	An unrecognized prefix was given
3849acbeddcSDevin Teske# 	3	Result too large to calculate
385a96ea66fSDevin Teske#
386a96ea66fSDevin Teskef_expand_number()
387a96ea66fSDevin Teske{
388a96ea66fSDevin Teske	local __string="$1" __var_to_set="$2"
38905a0a04aSDevin Teske	local __cp __num __bshift __maxinput
390a96ea66fSDevin Teske
39105a0a04aSDevin Teske	# Remove any leading non-digits
392ae978c36SDevin Teske	__string="${__string#${__string%%[0-9]*}}"
39305a0a04aSDevin Teske
39405a0a04aSDevin Teske	# Store the numbers (no trailing suffix)
39505a0a04aSDevin Teske	__num="${__string%%[!0-9]*}"
396a96ea66fSDevin Teske
3979acbeddcSDevin Teske	# Produce `-1' if string didn't contain any digits
39805a0a04aSDevin Teske	if [ ! "$__num" ]; then
399a96ea66fSDevin Teske		if [ "$__var_to_set" ]; then
400a96ea66fSDevin Teske			setvar "$__var_to_set" -1
401a96ea66fSDevin Teske		else
402a96ea66fSDevin Teske			echo -1
403a96ea66fSDevin Teske		fi
4049acbeddcSDevin Teske		return 1 # 1 = "Given $string contains no digits"
405a96ea66fSDevin Teske	fi
406a96ea66fSDevin Teske
407a96ea66fSDevin Teske	# Remove all the leading numbers from the string to get at the prefix
40805a0a04aSDevin Teske	__string="${__string#"$__num"}"
409a96ea66fSDevin Teske
4109acbeddcSDevin Teske	#
4119acbeddcSDevin Teske	# Test for invalid prefix (and determine bitshift length)
4129acbeddcSDevin Teske	#
413a96ea66fSDevin Teske	case "$__string" in
4149acbeddcSDevin Teske	""|[[:space:]]*) # Shortcut
4159acbeddcSDevin Teske		if [ "$__var_to_set" ]; then
41605a0a04aSDevin Teske			setvar "$__var_to_set" $__num
4179acbeddcSDevin Teske		else
41805a0a04aSDevin Teske			echo $__num
4199acbeddcSDevin Teske		fi
4209acbeddcSDevin Teske		return $SUCCESS ;;
4219acbeddcSDevin Teske	[Kk]*) __bshift=10 ;;
4229acbeddcSDevin Teske	[Mm]*) __bshift=20 ;;
4239acbeddcSDevin Teske	[Gg]*) __bshift=30 ;;
4249acbeddcSDevin Teske	[Tt]*) __bshift=40 ;;
4259acbeddcSDevin Teske	[Pp]*) __bshift=50 ;;
4269acbeddcSDevin Teske	[Ee]*) __bshift=60 ;;
427a96ea66fSDevin Teske	*)
428a96ea66fSDevin Teske		# Unknown prefix
429a96ea66fSDevin Teske		if [ "$__var_to_set" ]; then
430a96ea66fSDevin Teske			setvar "$__var_to_set" -1
431a96ea66fSDevin Teske		else
432a96ea66fSDevin Teske			echo -1
433a96ea66fSDevin Teske		fi
4349acbeddcSDevin Teske		return 2 # 2 = "An unrecognized prefix was given"
435a96ea66fSDevin Teske	esac
436a96ea66fSDevin Teske
4379acbeddcSDevin Teske	# Determine if the wheels fall off
4389acbeddcSDevin Teske	__maxinput=$(( 0x7fffffffffffffff >> $__bshift ))
43905a0a04aSDevin Teske	if [ $__num -gt $__maxinput ]; then
4409acbeddcSDevin Teske		# Input (before expanding) would exceed 64-bit signed int
441a96ea66fSDevin Teske		if [ "$__var_to_set" ]; then
442a96ea66fSDevin Teske			setvar "$__var_to_set" -1
443a96ea66fSDevin Teske		else
444a96ea66fSDevin Teske			echo -1
445a96ea66fSDevin Teske		fi
4469acbeddcSDevin Teske		return 3 # 3 = "Result too large to calculate"
447a96ea66fSDevin Teske	fi
448a96ea66fSDevin Teske
4499acbeddcSDevin Teske	# Shift the number out and produce it
45005a0a04aSDevin Teske	__num=$(( $__num << $__bshift ))
451a96ea66fSDevin Teske	if [ "$__var_to_set" ]; then
45205a0a04aSDevin Teske		setvar "$__var_to_set" $__num
453a96ea66fSDevin Teske	else
45405a0a04aSDevin Teske		echo $__num
455a96ea66fSDevin Teske	fi
456a96ea66fSDevin Teske}
457a96ea66fSDevin Teske
4580f9fec9dSDevin Teske# f_longest_line_length
4590f9fec9dSDevin Teske#
4600f9fec9dSDevin Teske# Simple wrapper to an awk(1) script to print the length of the longest line of
4610f9fec9dSDevin Teske# input (read from stdin). Supports the newline escape-sequence `\n' for
4620f9fec9dSDevin Teske# splitting a single line into multiple lines.
4630f9fec9dSDevin Teske#
4640f9fec9dSDevin Teskef_longest_line_length_awk='
4650f9fec9dSDevin TeskeBEGIN { longest = 0 }
4660f9fec9dSDevin Teske{
4670f9fec9dSDevin Teske	if (split($0, lines, /\\n/) > 1)
4680f9fec9dSDevin Teske	{
4690f9fec9dSDevin Teske		for (n in lines)
4700f9fec9dSDevin Teske		{
4710f9fec9dSDevin Teske			len = length(lines[n])
4720f9fec9dSDevin Teske			longest = ( len > longest ? len : longest )
4730f9fec9dSDevin Teske		}
4740f9fec9dSDevin Teske	}
4750f9fec9dSDevin Teske	else
4760f9fec9dSDevin Teske	{
4770f9fec9dSDevin Teske		len = length($0)
4780f9fec9dSDevin Teske		longest = ( len > longest ? len : longest )
4790f9fec9dSDevin Teske	}
4800f9fec9dSDevin Teske}
4810f9fec9dSDevin TeskeEND { print longest }
4820f9fec9dSDevin Teske'
4830f9fec9dSDevin Teskef_longest_line_length()
4840f9fec9dSDevin Teske{
4850f9fec9dSDevin Teske	awk "$f_longest_line_length_awk"
4860f9fec9dSDevin Teske}
4870f9fec9dSDevin Teske
4880f9fec9dSDevin Teske# f_number_of_lines
4890f9fec9dSDevin Teske#
4900f9fec9dSDevin Teske# Simple wrapper to an awk(1) script to print the number of lines read from
4910f9fec9dSDevin Teske# stdin. Supports newline escape-sequence `\n' for splitting a single line into
4920f9fec9dSDevin Teske# multiple lines.
4930f9fec9dSDevin Teske#
4940f9fec9dSDevin Teskef_number_of_lines_awk='
4950f9fec9dSDevin TeskeBEGIN { num_lines = 0 }
4960f9fec9dSDevin Teske{
4970f9fec9dSDevin Teske	num_lines += split(" "$0, unused, /\\n/)
4980f9fec9dSDevin Teske}
4990f9fec9dSDevin TeskeEND { print num_lines }
5000f9fec9dSDevin Teske'
5010f9fec9dSDevin Teskef_number_of_lines()
5020f9fec9dSDevin Teske{
5030f9fec9dSDevin Teske	awk "$f_number_of_lines_awk"
5040f9fec9dSDevin Teske}
5050f9fec9dSDevin Teske
5060f9fec9dSDevin Teske# f_uriencode [$text]
5070f9fec9dSDevin Teske#
5080f9fec9dSDevin Teske# Encode $text for the purpose of embedding safely into a URL. Non-alphanumeric
5090f9fec9dSDevin Teske# characters are converted to `%XX' sequence where XX represents the hexa-
5100f9fec9dSDevin Teske# decimal ordinal of the non-alphanumeric character. If $text is missing, data
5110f9fec9dSDevin Teske# is instead read from standard input.
5120f9fec9dSDevin Teske#
5130f9fec9dSDevin Teskef_uriencode_awk='
5140f9fec9dSDevin TeskeBEGIN {
5150f9fec9dSDevin Teske	output = ""
5160f9fec9dSDevin Teske	for (n = 0; n < 256; n++) pack[sprintf("%c", n)] = sprintf("%%%02x", n)
5170f9fec9dSDevin Teske}
5180f9fec9dSDevin Teske{
5190f9fec9dSDevin Teske	sline = ""
5200f9fec9dSDevin Teske	slen = length($0)
5210f9fec9dSDevin Teske	for (n = 1; n <= slen; n++) {
5220f9fec9dSDevin Teske		char = substr($0, n, 1)
5230f9fec9dSDevin Teske		if ( char !~ /^[[:alnum:]_]$/ ) char = pack[char]
5240f9fec9dSDevin Teske		sline = sline char
5250f9fec9dSDevin Teske	}
5260f9fec9dSDevin Teske	output = output ( output ? "%0a" : "" ) sline
5270f9fec9dSDevin Teske}
5280f9fec9dSDevin TeskeEND { print output }
5290f9fec9dSDevin Teske'
5300f9fec9dSDevin Teskef_uriencode()
5310f9fec9dSDevin Teske{
5320f9fec9dSDevin Teske	if [ $# -gt 0 ]; then
5330f9fec9dSDevin Teske		echo "$1" | awk "$f_uriencode_awk"
5340f9fec9dSDevin Teske	else
5350f9fec9dSDevin Teske		awk "$f_uriencode_awk"
5360f9fec9dSDevin Teske	fi
5370f9fec9dSDevin Teske}
5380f9fec9dSDevin Teske
5390f9fec9dSDevin Teske# f_uridecode [$text]
5400f9fec9dSDevin Teske#
5410f9fec9dSDevin Teske# Decode $text from a URI. Encoded characters are converted from their `%XX'
5420f9fec9dSDevin Teske# sequence into original unencoded ASCII sequences. If $text is missing, data
5430f9fec9dSDevin Teske# is instead read from standard input.
5440f9fec9dSDevin Teske#
5450f9fec9dSDevin Teskef_uridecode_awk='
5460f9fec9dSDevin TeskeBEGIN { for (n = 0; n < 256; n++) chr[n] = sprintf("%c", n) }
5470f9fec9dSDevin Teske{
5480f9fec9dSDevin Teske	sline = ""
5490f9fec9dSDevin Teske	slen = length($0)
5500f9fec9dSDevin Teske	for (n = 1; n <= slen; n++)
5510f9fec9dSDevin Teske	{
5520f9fec9dSDevin Teske		seq = substr($0, n, 3)
5530f9fec9dSDevin Teske		if ( seq ~ /^%[[:xdigit:]][[:xdigit:]]$/ ) {
5540f9fec9dSDevin Teske			hex = substr(seq, 2, 2)
5550f9fec9dSDevin Teske			sline = sline chr[sprintf("%u", "0x"hex)]
5560f9fec9dSDevin Teske			n += 2
5570f9fec9dSDevin Teske		} else
5580f9fec9dSDevin Teske			sline = sline substr(seq, 1, 1)
5590f9fec9dSDevin Teske	}
5600f9fec9dSDevin Teske	print sline
5610f9fec9dSDevin Teske}
5620f9fec9dSDevin Teske'
5630f9fec9dSDevin Teskef_uridecode()
5640f9fec9dSDevin Teske{
5650f9fec9dSDevin Teske	if [ $# -gt 0 ]; then
5660f9fec9dSDevin Teske		echo "$1" | awk "$f_uridecode_awk"
5670f9fec9dSDevin Teske	else
5680f9fec9dSDevin Teske		awk "$f_uridecode_awk"
5690f9fec9dSDevin Teske	fi
5700f9fec9dSDevin Teske}
5710f9fec9dSDevin Teske
572d3a0f918SDevin Teske############################################################ MAIN
573d3a0f918SDevin Teske
57456961fd7SDevin Teskef_dprintf "%s: Successfully loaded." strings.subr
57556961fd7SDevin Teske
576ab2043b8SDevin Teskefi # ! $_STRINGS_SUBR
577