aboutsummaryrefslogtreecommitdiff
path: root/scripts/functions.sh
blob: 8ae765859e511670090e54664673d5f881f2512f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
#! /bin/sh
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2018-2023 Gavin D. Howard and contributors.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

# This script is NOT meant to be run! It is meant to be sourced by other
# scripts.

# Reads and follows a link until it finds a real file. This is here because the
# readlink utility is not part of the POSIX standard. Sigh...
# @param f  The link to find the original file for.
readlink() {

	_readlink_f="$1"
	shift

	_readlink_arrow="-> "
	_readlink_d=$(dirname "$_readlink_f")

	_readlink_lsout=""
	_readlink_link=""

	_readlink_lsout=$(ls -dl "$_readlink_f")
	_readlink_link=$(printf '%s' "${_readlink_lsout#*$_readlink_arrow}")

	while [ -z "${_readlink_lsout##*$_readlink_arrow*}" ]; do
		_readlink_f="$_readlink_d/$_readlink_link"
		_readlink_d=$(dirname "$_readlink_f")
		_readlink_lsout=$(ls -dl "$_readlink_f")
		_readlink_link=$(printf '%s' "${_readlink_lsout#*$_readlink_arrow}")
	done

	printf '%s' "${_readlink_f##*$_readlink_d/}"
}

# Quick function for exiting with an error.
# @param 1  A message to print.
# @param 2  The exit code to use.
err_exit() {

	if [ "$#" -ne 2 ]; then
		printf 'Invalid number of args to err_exit\n'
		exit 1
	fi

	printf '%s\n' "$1"
	exit "$2"
}

# Function for checking the "d"/"dir" argument of scripts. This function expects
# a usage() function to exist in the caller.
# @param 1  The argument to check.
check_d_arg() {

	if [ "$#" -ne 1 ]; then
		printf 'Invalid number of args to check_d_arg\n'
		exit 1
	fi

	_check_d_arg_arg="$1"
	shift

	if [ "$_check_d_arg_arg" != "bc" ] && [ "$_check_d_arg_arg" != "dc" ]; then
		_check_d_arg_msg=$(printf 'Invalid d arg: %s\nMust be either "bc" or "dc".\n\n' \
			"$_check_d_arg_arg")
		usage "$_check_d_arg_msg"
	fi
}

# Function for checking the boolean arguments of scripts. This function expects
# a usage() function to exist in the caller.
# @param 1  The argument to check.
check_bool_arg() {

	if [ "$#" -ne 1 ]; then
		printf 'Invalid number of args to check_bool_arg\n'
		exit 1
	fi

	_check_bool_arg_arg="$1"
	shift

	if [ "$_check_bool_arg_arg" != "0" ] && [ "$_check_bool_arg_arg" != "1" ]; then
		_check_bool_arg_msg=$(printf 'Invalid bool arg: %s\nMust be either "0" or "1".\n\n' \
			"$_check_bool_arg_arg")
		usage "$_check_bool_arg_msg"
	fi
}

# Function for checking the executable arguments of scripts. This function
# expects a usage() function to exist in the caller.
# @param 1  The argument to check.
check_exec_arg() {

	if [ "$#" -ne 1 ]; then
		printf 'Invalid number of args to check_exec_arg\n'
		exit 1
	fi

	_check_exec_arg_arg="$1"
	shift

	if [ ! -x "$_check_exec_arg_arg" ]; then
		if ! command -v "$_check_exec_arg_arg" >/dev/null 2>&1; then
			_check_exec_arg_msg=$(printf 'Invalid exec arg: %s\nMust be an executable file.\n\n' \
				"$_check_exec_arg_arg")
			usage "$_check_exec_arg_msg"
		fi
	fi
}

# Function for checking the file arguments of scripts. This function expects a
# usage() function to exist in the caller.
# @param 1  The argument to check.
check_file_arg() {

	if [ "$#" -ne 1 ]; then
		printf 'Invalid number of args to check_file_arg\n'
		exit 1
	fi

	_check_file_arg_arg="$1"
	shift

	if [ ! -f "$_check_file_arg_arg" ]; then
		_check_file_arg_msg=$(printf 'Invalid file arg: %s\nMust be a file.\n\n' \
			"$_check_file_arg_arg")
		usage "$_check_file_arg_msg"
	fi
}

# Check the return code on a test and exit with a fail if it's non-zero.
# @param d     The calculator under test.
# @param err   The return code.
# @param name  The name of the test.
checktest_retcode() {

	_checktest_retcode_d="$1"
	shift

	_checktest_retcode_err="$1"
	shift

	_checktest_retcode_name="$1"
	shift

	if [ "$_checktest_retcode_err" -ne 0 ]; then
		printf 'FAIL!!!\n'
		err_exit "$_checktest_retcode_d failed test '$_checktest_retcode_name' with error code $_checktest_retcode_err" 1
	fi
}

# Check the result of a test. First, it checks the error code using
# checktest_retcode(). Then it checks the output against the expected output
# and fails if it doesn't match.
# @param d             The calculator under test.
# @param err           The error code.
# @param name          The name of the test.
# @param test_path     The path to the test.
# @param results_name  The path to the file with the expected result.
checktest() {

	_checktest_d="$1"
	shift

	_checktest_err="$1"
	shift

	_checktest_name="$1"
	shift

	_checktest_test_path="$1"
	shift

	_checktest_results_name="$1"
	shift

	checktest_retcode "$_checktest_d" "$_checktest_err" "$_checktest_name"

	_checktest_diff=$(diff "$_checktest_test_path" "$_checktest_results_name")

	_checktest_err="$?"

	if [ "$_checktest_err" -ne 0 ]; then
		printf 'FAIL!!!\n'
		printf '%s\n' "$_checktest_diff"
		err_exit "$_checktest_d failed test $_checktest_name" 1
	fi
}

# Die. With a message.
# @param d     The calculator under test.
# @param msg   The message to print.
# @param name  The name of the test.
# @param err   The return code from the test.
die() {

	_die_d="$1"
	shift

	_die_msg="$1"
	shift

	_die_name="$1"
	shift

	_die_err="$1"
	shift

	_die_str=$(printf '\n%s %s on test:\n\n    %s\n' "$_die_d" "$_die_msg" "$_die_name")

	err_exit "$_die_str" "$_die_err"
}

# Check that a test did not crash and die if it did.
# @param d      The calculator under test.
# @param error  The error code.
# @param name   The name of the test.
checkcrash() {

	_checkcrash_d="$1"
	shift

	_checkcrash_error="$1"
	shift

	_checkcrash_name="$1"
	shift


	if [ "$_checkcrash_error" -gt 127 ]; then
		die "$_checkcrash_d" "crashed ($_checkcrash_error)" \
			"$_checkcrash_name" "$_checkcrash_error"
	fi
}

# Check that a test had an error or crash.
# @param d        The calculator under test.
# @param error    The error code.
# @param name     The name of the test.
# @param out      The file that the test results were output to.
# @param exebase  The name of the executable.
checkerrtest()
{
	_checkerrtest_d="$1"
	shift

	_checkerrtest_error="$1"
	shift

	_checkerrtest_name="$1"
	shift

	_checkerrtest_out="$1"
	shift

	_checkerrtest_exebase="$1"
	shift

	checkcrash "$_checkerrtest_d" "$_checkerrtest_error" "$_checkerrtest_name"

	if [ "$_checkerrtest_error" -eq 0 ]; then
		die "$_checkerrtest_d" "returned no error" "$_checkerrtest_name" 127
	fi

	# This is to check for memory errors with Valgrind, which is told to return
	# 100 on memory errors.
	if [ "$_checkerrtest_error" -eq 100 ]; then

		_checkerrtest_output=$(cat "$_checkerrtest_out")
		_checkerrtest_fatal_error="Fatal error"

		if [ "${_checkerrtest_output##*$_checkerrtest_fatal_error*}" ]; then
			printf "%s\n" "$_checkerrtest_output"
			die "$_checkerrtest_d" "had memory errors on a non-fatal error" \
				"$_checkerrtest_name" "$_checkerrtest_error"
		fi
	fi

	if [ ! -s "$_checkerrtest_out" ]; then
		die "$_checkerrtest_d" "produced no error message" "$_checkerrtest_name" "$_checkerrtest_error"
	fi

	# To display error messages, uncomment this line. This is useful when
	# debugging.
	#cat "$_checkerrtest_out"
}

# Replace a substring in a string with another. This function is the *real*
# workhorse behind configure.sh's generation of a Makefile.
#
# This function uses a sed call that uses exclamation points `!` as delimiters.
# As a result, needle can never contain an exclamation point. Oh well.
#
# @param str          The string that will have any of the needle replaced by
#                     replacement.
# @param needle       The needle to replace in str with replacement.
# @param replacement  The replacement for needle in str.
substring_replace() {

	_substring_replace_str="$1"
	shift

	_substring_replace_needle="$1"
	shift

	_substring_replace_replacement="$1"
	shift

	_substring_replace_result=$(printf '%s\n' "$_substring_replace_str" | \
		sed -e "s!$_substring_replace_needle!$_substring_replace_replacement!g")

	printf '%s' "$_substring_replace_result"
}

# Generates an NLS path based on the locale and executable name.
#
# This is a monstrosity for a reason.
#
# @param nlspath   The $NLSPATH
# @param locale    The locale.
# @param execname  The name of the executable.
gen_nlspath() {

	_gen_nlspath_nlspath="$1"
	shift

	_gen_nlspath_locale="$1"
	shift

	_gen_nlspath_execname="$1"
	shift

	# Split the locale into its modifier and other parts.
	_gen_nlspath_char="@"
	_gen_nlspath_modifier="${_gen_nlspath_locale#*$_gen_nlspath_char}"
	_gen_nlspath_tmplocale="${_gen_nlspath_locale%%$_gen_nlspath_char*}"

	# Split the locale into charset and other parts.
	_gen_nlspath_char="."
	_gen_nlspath_charset="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}"
	_gen_nlspath_tmplocale="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}"

	# Check for an empty charset.
	if [ "$_gen_nlspath_charset" = "$_gen_nlspath_tmplocale" ]; then
		_gen_nlspath_charset=""
	fi

	# Split the locale into territory and language.
	_gen_nlspath_char="_"
	_gen_nlspath_territory="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}"
	_gen_nlspath_language="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}"

	# Check for empty territory and language.
	if [ "$_gen_nlspath_territory" = "$_gen_nlspath_tmplocale" ]; then
		_gen_nlspath_territory=""
	fi

	if [ "$_gen_nlspath_language" = "$_gen_nlspath_tmplocale" ]; then
		_gen_nlspath_language=""
	fi

	# Prepare to replace the format specifiers. This is done by wrapping the in
	# pipe characters. It just makes it easier to split them later.
	_gen_nlspath_needles="%%:%L:%N:%l:%t:%c"

	_gen_nlspath_needles=$(printf '%s' "$_gen_nlspath_needles" | tr ':' '\n')

	for _gen_nlspath_i in $_gen_nlspath_needles; do
		_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "$_gen_nlspath_i" "|$_gen_nlspath_i|")
	done

	# Replace all the format specifiers.
	_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%%" "%")
	_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%L" "$_gen_nlspath_locale")
	_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%N" "$_gen_nlspath_execname")
	_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%l" "$_gen_nlspath_language")
	_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%t" "$_gen_nlspath_territory")
	_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%c" "$_gen_nlspath_charset")

	# Get rid of pipe characters.
	_gen_nlspath_nlspath=$(printf '%s' "$_gen_nlspath_nlspath" | tr -d '|')

	# Return the result.
	printf '%s' "$_gen_nlspath_nlspath"
}

ALL=0
NOSKIP=1
SKIP=2

# Filters text out of a file according to the build type.
# @param in    File to filter.
# @param out   File to write the filtered output to.
# @param type  Build type.
filter_text() {

	_filter_text_in="$1"
	shift

	_filter_text_out="$1"
	shift

	_filter_text_buildtype="$1"
	shift

	# Set up some local variables.
	_filter_text_status="$ALL"
	_filter_text_last_line=""

	# We need to set IFS, so we store it here for restoration later.
	_filter_text_ifs="$IFS"

	# Remove the file- that will be generated.
	rm -rf "$_filter_text_out"

	# Here is the magic. This loop reads the template line-by-line, and based on
	# _filter_text_status, either prints it to the markdown manual or not.
	#
	# Here is how the template is set up: it is a normal markdown file except
	# that there are sections surrounded tags that look like this:
	#
	# {{ <build_type_list> }}
	# ...
	# {{ end }}
	#
	# Those tags mean that whatever build types are found in the
	# <build_type_list> get to keep that section. Otherwise, skip.
	#
	# Obviously, the tag itself and its end are not printed to the markdown
	# manual.
	while IFS= read -r _filter_text_line; do

		# If we have found an end, reset the status.
		if [ "$_filter_text_line" = "{{ end }}" ]; then

			# Some error checking. This helps when editing the templates.
			if [ "$_filter_text_status" -eq "$ALL" ]; then
				err_exit "{{ end }} tag without corresponding start tag" 2
			fi

			_filter_text_status="$ALL"

		# We have found a tag that allows our build type to use it.
		elif [ "${_filter_text_line#\{\{* $_filter_text_buildtype *\}\}}" != "$_filter_text_line" ]; then

			# More error checking. We don't want tags nested.
			if [ "$_filter_text_status" -ne "$ALL" ]; then
				err_exit "start tag nested in start tag" 3
			fi

			_filter_text_status="$NOSKIP"

		# We have found a tag that is *not* allowed for our build type.
		elif [ "${_filter_text_line#\{\{*\}\}}" != "$_filter_text_line" ]; then

			if [ "$_filter_text_status" -ne "$ALL" ]; then
				err_exit "start tag nested in start tag" 3
			fi

			_filter_text_status="$SKIP"

		# This is for normal lines. If we are not skipping, print.
		else
			if [ "$_filter_text_status" -ne "$SKIP" ]; then
				if [ "$_filter_text_line" != "$_filter_text_last_line" ]; then
					printf '%s\n' "$_filter_text_line" >> "$_filter_text_out"
				fi
				_filter_text_last_line="$_filter_text_line"
			fi
		fi

	done < "$_filter_text_in"

	# Reset IFS.
	IFS="$_filter_text_ifs"
}