xref: /freebsd/usr.sbin/bsdconfig/share/strings.subr (revision 8e37a7c8b904c50714931a3ef635023dd658499e)
1if [ ! "$_STRINGS_SUBR" ]; then _STRINGS_SUBR=1
2#
3# Copyright (c) 2006-2013 Devin Teske
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27# $FreeBSD$
28#
29############################################################ GLOBALS
30
31#
32# Valid characters that can appear in an sh(1) variable name
33#
34# Please note that the character ranges A-Z and a-z should be avoided because
35# these can include accent characters (which are not valid in a variable name).
36# For example, A-Z matches any character that sorts after A but before Z,
37# including A and Z. Although ASCII order would make more sense, that is not
38# how it works.
39#
40VALID_VARNAME_CHARS="0-9ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
41
42############################################################ FUNCTIONS
43
44# f_substr "$string" $start [ $length ]
45#
46# Simple wrapper to awk(1)'s `substr' function.
47#
48f_substr()
49{
50	local string="$1" start="${2:-0}" len="${3:-0}"
51	echo "$string" | awk "{ print substr(\$0, $start, $len) }"
52}
53
54# f_snprintf $var_to_set $size $format ...
55#
56# Similar to snprintf(3), write at most $size number of bytes into $var_to_set
57# using printf(1) syntax (`$format ...'). The value of $var_to_set is NULL
58# unless at-least one byte is stored from the output.
59#
60f_snprintf()
61{
62	local __var_to_set="$1" __size="$2"
63	shift 2 # var_to_set/size
64	eval "$__var_to_set"=\$\( printf \"\$@\" \| awk -v max=\"\$__size\" \''
65	{
66		len = length($0)
67		max -= len
68		print substr($0,0,(max > 0 ? len : max + len))
69		if ( max < 0 ) exit
70		max--
71	}'\' \)
72}
73
74# f_vsnprintf $var_to_set $size $format $format_args
75#
76# Similar to vsnprintf(3), write at most $size number of bytes into $var_to_set
77# using printf(1) syntax (`$format $format_args'). The value of $var_to_set is
78# NULL unless at-least one byte is stored from the output.
79#
80# Example 1:
81#
82# 	limit=7 format="%s"
83# 	format_args="'abc   123'" # 3-spaces between abc and 123
84# 	f_vsnprintf foo $limit "$format" "$format_args" # foo=[abc   1]
85#
86# Example 2:
87#
88# 	limit=12 format="%s %s"
89# 	format_args="   'doghouse'      'foxhound'   "
90# 		# even more spaces added to illustrate escape-method
91# 	f_vsnprintf foo $limit "$format" "$format_args" # foo=[doghouse fox]
92#
93# Example 3:
94#
95# 	limit=13 format="%s %s"
96# 	f_shell_escape arg1 'aaa"aaa' # arg1=[aaa"aaa] (no change)
97# 	f_shell_escape arg2 "aaa'aaa" # arg2=[aaa'\''aaa] (escaped s-quote)
98# 	format_args="'$arg1' '$arg2'" # use single-quotes to surround args
99# 	f_vsnprintf foo $limit "$format" "$format_args" # foo=[aaa"aaa aaa'a]
100#
101# In all of the above examples, the call to f_vsnprintf() does not change. Only
102# the contents of $limit, $format, and $format_args changes in each example.
103#
104f_vsnprintf()
105{
106	eval f_snprintf \"\$1\" \"\$2\" \"\$3\" $4
107}
108
109# f_longest_line_length
110#
111# Simple wrapper to an awk(1) script to print the length of the longest line of
112# input (read from stdin). Supports the newline escape-sequence `\n' for
113# splitting a single line into multiple lines.
114#
115f_longest_line_length_awk='
116BEGIN { longest = 0 }
117{
118	if (split($0, lines, /\\n/) > 1)
119	{
120		for (n in lines)
121		{
122			len = length(lines[n])
123			longest = ( len > longest ? len : longest )
124		}
125	}
126	else
127	{
128		len = length($0)
129		longest = ( len > longest ? len : longest )
130	}
131}
132END { print longest }
133'
134f_longest_line_length()
135{
136	awk "$f_longest_line_length_awk"
137}
138
139# f_number_of_lines
140#
141# Simple wrapper to an awk(1) script to print the number of lines read from
142# stdin. Supports newline escape-sequence `\n' for splitting a single line into
143# multiple lines.
144#
145f_number_of_lines_awk='
146BEGIN { num_lines = 0 }
147{
148	num_lines += split(" "$0, unused, /\\n/)
149}
150END { print num_lines }
151'
152f_number_of_lines()
153{
154	awk "$f_number_of_lines_awk"
155}
156
157# f_isinteger $arg
158#
159# Returns true if argument is a positive/negative whole integer.
160#
161f_isinteger()
162{
163	local arg="$1"
164
165	# Prevent division-by-zero
166	[ "$arg" = "0" ] && return $SUCCESS
167
168	# Attempt to perform arithmetic divison (an operation which will exit
169	# with error unless arg is a valid positive/negative whole integer).
170	#
171	( : $((0/$arg)) ) > /dev/null 2>&1
172}
173
174# f_uriencode [$text]
175#
176# Encode $text for the purpose of embedding safely into a URL. Non-alphanumeric
177# characters are converted to `%XX' sequence where XX represents the hexa-
178# decimal ordinal of the non-alphanumeric character. If $text is missing, data
179# is instead read from standard input.
180#
181f_uriencode_awk='
182BEGIN {
183	output = ""
184	for (n = 0; n < 256; n++) pack[sprintf("%c", n)] = sprintf("%%%02x", n)
185}
186{
187	sline = ""
188	slen = length($0)
189	for (n = 1; n <= slen; n++) {
190		char = substr($0, n, 1)
191		if ( char !~ /^[[:alnum:]_]$/ ) char = pack[char]
192		sline = sline char
193	}
194	output = output ( output ? "%0a" : "" ) sline
195}
196END { print output }
197'
198f_uriencode()
199{
200	if [ $# -gt 0 ]; then
201		echo "$1" | awk "$f_uriencode_awk"
202	else
203		awk "$f_uriencode_awk"
204	fi
205}
206
207# f_uridecode [$text]
208#
209# Decode $text from a URI. Encoded characters are converted from their `%XX'
210# sequence into original unencoded ASCII sequences. If $text is missing, data
211# is instead read from standard input.
212#
213f_uridecode_awk='
214BEGIN { for (n = 0; n < 256; n++) chr[n] = sprintf("%c", n) }
215{
216	sline = ""
217	slen = length($0)
218	for (n = 1; n <= slen; n++)
219	{
220		seq = substr($0, n, 3)
221		if ( seq ~ /^%[[:xdigit:]][[:xdigit:]]$/ ) {
222			hex = substr(seq, 2, 2)
223			sline = sline chr[sprintf("%u", "0x"hex)]
224			n += 2
225		} else
226			sline = sline substr(seq, 1, 1)
227	}
228	print sline
229}
230'
231f_uridecode()
232{
233	if [ $# -gt 0 ]; then
234		echo "$1" | awk "$f_uridecode_awk"
235	else
236		awk "$f_uridecode_awk"
237	fi
238}
239
240# f_replaceall $string $find $replace [$var_to_set]
241#
242# Replace all occurrences of $find in $string with $replace. If $var_to_set is
243# either missing or NULL, the variable name is produced on standard out for
244# capturing in a sub-shell (which is less recommended due to performance
245# degradation).
246#
247f_replaceall()
248{
249	local __left="" __right="$1"
250	local __find="$2" __replace="$3" __var_to_set="$4"
251	while :; do
252		case "$__right" in *$__find*)
253			__left="$__left${__right%%$__find*}$__replace"
254			__right="${__right#*$__find}"
255			continue
256		esac
257		break
258	done
259	__left="$__left${__right#*$__find}"
260	if [ "$__var_to_set" ]; then
261		setvar "$__var_to_set" "$__left"
262	else
263		echo "$__left"
264	fi
265}
266
267# f_str2varname $string [$var_to_set]
268#
269# Convert a string into a suitable value to be used as a variable name
270# by converting unsuitable characters into the underscrore [_]. If $var_to_set
271# is either missing or NULL, the variable name is produced on standard out for
272# capturing in a sub-shell (which is less recommended due to performance
273# degradation).
274#
275f_str2varname()
276{
277	local __string="$1" __var_to_set="$2"
278	f_replaceall "$__string" "[!$VALID_VARNAME_CHARS]" "_" "$__var_to_set"
279}
280
281# f_shell_escape $string [$var_to_set]
282#
283# Escape $string for shell eval statement(s) by replacing all single-quotes
284# with a special sequence that creates a compound string when interpolated
285# by eval with surrounding single-quotes.
286#
287# For example:
288#
289# 	foo="abc'123"
290# 	f_shell_escape "$foo" bar # bar=[abc'\''123]
291# 	eval echo \'$bar\' # produces abc'123
292#
293# This is helpful when processing an argument list that has to retain its
294# escaped structure for later evaluations.
295#
296# WARNING: Surrounding single-quotes are not added; this is the responsibility
297# of the code passing the escaped values to eval (which also aids readability).
298#
299f_shell_escape()
300{
301	local __string="$1" __var_to_set="$2"
302	f_replaceall "$__string" "'" "'\\''" "$__var_to_set"
303}
304
305# f_shell_unescape $string [$var_to_set]
306#
307# The antithesis of f_shell_escape(), this function takes an escaped $string
308# and expands it.
309#
310# For example:
311#
312# 	foo="abc'123"
313# 	f_shell_escape "$foo" bar # bar=[abc'\''123]
314# 	f_shell_unescape "$bar" # produces abc'123
315#
316f_shell_unescape()
317{
318	local __string="$1" __var_to_set="$2"
319	f_replaceall "$__string" "'\\''" "'" "$__var_to_set"
320}
321
322############################################################ MAIN
323
324f_dprintf "%s: Successfully loaded." strings.subr
325
326fi # ! $_STRINGS_SUBR
327