#! /bin/sh1#2# SPDX-License-Identifier: BSD-2-Clause3#4# Copyright (c) 2018-2025 Gavin D. Howard and contributors.5#6# Redistribution and use in source and binary forms, with or without7# modification, are permitted provided that the following conditions are met:8#9# * Redistributions of source code must retain the above copyright notice, this10# list of conditions and the following disclaimer.11#12# * Redistributions in binary form must reproduce the above copyright notice,13# this list of conditions and the following disclaimer in the documentation14# and/or other materials provided with the distribution.15#16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE26# POSSIBILITY OF SUCH DAMAGE.27#2829# This script is NOT meant to be run! It is meant to be sourced by other30# scripts.3132# Reads and follows a link until it finds a real file. This is here because the33# readlink utility is not part of the POSIX standard. Sigh...34# @param f The link to find the original file for.35readlink() {3637_readlink_f="$1"38shift3940_readlink_arrow="-> "41_readlink_d=$(dirname "$_readlink_f")4243_readlink_lsout=""44_readlink_link=""4546_readlink_lsout=$(ls -dl "$_readlink_f")47_readlink_link=$(printf '%s' "${_readlink_lsout#*$_readlink_arrow}")4849while [ -z "${_readlink_lsout##*$_readlink_arrow*}" ]; do50_readlink_f="$_readlink_d/$_readlink_link"51_readlink_d=$(dirname "$_readlink_f")52_readlink_lsout=$(ls -dl "$_readlink_f")53_readlink_link=$(printf '%s' "${_readlink_lsout#*$_readlink_arrow}")54done5556printf '%s' "${_readlink_f##*$_readlink_d/}"57}5859# Quick function for exiting with an error.60# @param 1 A message to print.61# @param 2 The exit code to use.62err_exit() {6364if [ "$#" -ne 2 ]; then65printf 'Invalid number of args to err_exit\n'66exit 167fi6869printf '%s\n' "$1"70exit "$2"71}7273# Function for checking the "d"/"dir" argument of scripts. This function expects74# a usage() function to exist in the caller.75# @param 1 The argument to check.76check_d_arg() {7778if [ "$#" -ne 1 ]; then79printf 'Invalid number of args to check_d_arg\n'80exit 181fi8283_check_d_arg_arg="$1"84shift8586if [ "$_check_d_arg_arg" != "bc" ] && [ "$_check_d_arg_arg" != "dc" ]; then87_check_d_arg_msg=$(printf 'Invalid d arg: %s\nMust be either "bc" or "dc".\n\n' \88"$_check_d_arg_arg")89usage "$_check_d_arg_msg"90fi91}9293# Function for checking the boolean arguments of scripts. This function expects94# a usage() function to exist in the caller.95# @param 1 The argument to check.96check_bool_arg() {9798if [ "$#" -ne 1 ]; then99printf 'Invalid number of args to check_bool_arg\n'100exit 1101fi102103_check_bool_arg_arg="$1"104shift105106if [ "$_check_bool_arg_arg" != "0" ] && [ "$_check_bool_arg_arg" != "1" ]; then107_check_bool_arg_msg=$(printf 'Invalid bool arg: %s\nMust be either "0" or "1".\n\n' \108"$_check_bool_arg_arg")109usage "$_check_bool_arg_msg"110fi111}112113# Function for checking the executable arguments of scripts. This function114# expects a usage() function to exist in the caller.115# @param 1 The argument to check.116check_exec_arg() {117118if [ "$#" -ne 1 ]; then119printf 'Invalid number of args to check_exec_arg\n'120exit 1121fi122123_check_exec_arg_arg="$1"124shift125126if [ ! -x "$_check_exec_arg_arg" ]; then127if ! command -v "$_check_exec_arg_arg" >/dev/null 2>&1; then128_check_exec_arg_msg=$(printf 'Invalid exec arg: %s\nMust be an executable file.\n\n' \129"$_check_exec_arg_arg")130usage "$_check_exec_arg_msg"131fi132fi133}134135# Function for checking the file arguments of scripts. This function expects a136# usage() function to exist in the caller.137# @param 1 The argument to check.138check_file_arg() {139140if [ "$#" -ne 1 ]; then141printf 'Invalid number of args to check_file_arg\n'142exit 1143fi144145_check_file_arg_arg="$1"146shift147148if [ ! -f "$_check_file_arg_arg" ]; then149_check_file_arg_msg=$(printf 'Invalid file arg: %s\nMust be a file.\n\n' \150"$_check_file_arg_arg")151usage "$_check_file_arg_msg"152fi153}154155# Check the return code on a test and exit with a fail if it's non-zero.156# @param d The calculator under test.157# @param err The return code.158# @param name The name of the test.159checktest_retcode() {160161_checktest_retcode_d="$1"162shift163164_checktest_retcode_err="$1"165shift166167_checktest_retcode_name="$1"168shift169170if [ "$_checktest_retcode_err" -ne 0 ]; then171printf 'FAIL!!!\n'172err_exit "$_checktest_retcode_d failed test '$_checktest_retcode_name' with error code $_checktest_retcode_err" 1173fi174}175176# Check the result of a test. First, it checks the error code using177# checktest_retcode(). Then it checks the output against the expected output178# and fails if it doesn't match.179# @param d The calculator under test.180# @param err The error code.181# @param name The name of the test.182# @param test_path The path to the test.183# @param results_name The path to the file with the expected result.184checktest() {185186_checktest_d="$1"187shift188189_checktest_err="$1"190shift191192_checktest_name="$1"193shift194195_checktest_test_path="$1"196shift197198_checktest_results_name="$1"199shift200201checktest_retcode "$_checktest_d" "$_checktest_err" "$_checktest_name"202203_checktest_diff=$(diff "$_checktest_test_path" "$_checktest_results_name")204205_checktest_err="$?"206207if [ "$_checktest_err" -ne 0 ]; then208printf 'FAIL!!!\n'209printf '%s\n' "$_checktest_diff"210err_exit "$_checktest_d failed test $_checktest_name" 1211fi212}213214# Die. With a message.215# @param d The calculator under test.216# @param msg The message to print.217# @param name The name of the test.218# @param err The return code from the test.219die() {220221_die_d="$1"222shift223224_die_msg="$1"225shift226227_die_name="$1"228shift229230_die_err="$1"231shift232233_die_str=$(printf '\n%s %s on test:\n\n %s\n' "$_die_d" "$_die_msg" "$_die_name")234235err_exit "$_die_str" "$_die_err"236}237238# Check that a test did not crash and die if it did.239# @param d The calculator under test.240# @param error The error code.241# @param name The name of the test.242checkcrash() {243244_checkcrash_d="$1"245shift246247_checkcrash_error="$1"248shift249250_checkcrash_name="$1"251shift252253254if [ "$_checkcrash_error" -gt 127 ]; then255die "$_checkcrash_d" "crashed ($_checkcrash_error)" \256"$_checkcrash_name" "$_checkcrash_error"257fi258}259260# Check that a test had an error or crash.261# @param d The calculator under test.262# @param error The error code.263# @param name The name of the test.264# @param out The file that the test results were output to.265# @param exebase The name of the executable.266checkerrtest()267{268_checkerrtest_d="$1"269shift270271_checkerrtest_error="$1"272shift273274_checkerrtest_name="$1"275shift276277_checkerrtest_out="$1"278shift279280_checkerrtest_exebase="$1"281shift282283checkcrash "$_checkerrtest_d" "$_checkerrtest_error" "$_checkerrtest_name"284285if [ "$_checkerrtest_error" -eq 0 ]; then286die "$_checkerrtest_d" "returned no error" "$_checkerrtest_name" 127287fi288289# This is to check for memory errors with Valgrind, which is told to return290# 100 on memory errors.291if [ "$_checkerrtest_error" -eq 100 ]; then292293_checkerrtest_output=$(cat "$_checkerrtest_out")294_checkerrtest_fatal_error="Fatal error"295296if [ "${_checkerrtest_output##*$_checkerrtest_fatal_error*}" ]; then297printf "%s\n" "$_checkerrtest_output"298die "$_checkerrtest_d" "had memory errors on a non-fatal error" \299"$_checkerrtest_name" "$_checkerrtest_error"300fi301fi302303if [ ! -s "$_checkerrtest_out" ]; then304die "$_checkerrtest_d" "produced no error message" "$_checkerrtest_name" "$_checkerrtest_error"305fi306307# To display error messages, uncomment this line. This is useful when308# debugging.309#cat "$_checkerrtest_out"310}311312# Replace a substring in a string with another. This function is the *real*313# workhorse behind configure.sh's generation of a Makefile.314#315# This function uses a sed call that uses exclamation points `!` as delimiters.316# As a result, needle can never contain an exclamation point. Oh well.317#318# @param str The string that will have any of the needle replaced by319# replacement.320# @param needle The needle to replace in str with replacement.321# @param replacement The replacement for needle in str.322substring_replace() {323324_substring_replace_str="$1"325shift326327_substring_replace_needle="$1"328shift329330_substring_replace_replacement="$1"331shift332333_substring_replace_result=$(printf '%s\n' "$_substring_replace_str" | \334sed -e "s!$_substring_replace_needle!$_substring_replace_replacement!g")335336printf '%s' "$_substring_replace_result"337}338339# Generates an NLS path based on the locale and executable name.340#341# This is a monstrosity for a reason.342#343# @param nlspath The $NLSPATH344# @param locale The locale.345# @param execname The name of the executable.346gen_nlspath() {347348_gen_nlspath_nlspath="$1"349shift350351_gen_nlspath_locale="$1"352shift353354_gen_nlspath_execname="$1"355shift356357# Split the locale into its modifier and other parts.358_gen_nlspath_char="@"359_gen_nlspath_modifier="${_gen_nlspath_locale#*$_gen_nlspath_char}"360_gen_nlspath_tmplocale="${_gen_nlspath_locale%%$_gen_nlspath_char*}"361362# Split the locale into charset and other parts.363_gen_nlspath_char="."364_gen_nlspath_charset="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}"365_gen_nlspath_tmplocale="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}"366367# Check for an empty charset.368if [ "$_gen_nlspath_charset" = "$_gen_nlspath_tmplocale" ]; then369_gen_nlspath_charset=""370fi371372# Split the locale into territory and language.373_gen_nlspath_char="_"374_gen_nlspath_territory="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}"375_gen_nlspath_language="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}"376377# Check for empty territory and language.378if [ "$_gen_nlspath_territory" = "$_gen_nlspath_tmplocale" ]; then379_gen_nlspath_territory=""380fi381382if [ "$_gen_nlspath_language" = "$_gen_nlspath_tmplocale" ]; then383_gen_nlspath_language=""384fi385386# Prepare to replace the format specifiers. This is done by wrapping the in387# pipe characters. It just makes it easier to split them later.388_gen_nlspath_needles="%%:%L:%N:%l:%t:%c"389390_gen_nlspath_needles=$(printf '%s' "$_gen_nlspath_needles" | tr ':' '\n')391392for _gen_nlspath_i in $_gen_nlspath_needles; do393_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "$_gen_nlspath_i" "|$_gen_nlspath_i|")394done395396# Replace all the format specifiers.397_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%%" "%")398_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%L" "$_gen_nlspath_locale")399_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%N" "$_gen_nlspath_execname")400_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%l" "$_gen_nlspath_language")401_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%t" "$_gen_nlspath_territory")402_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%c" "$_gen_nlspath_charset")403404# Get rid of pipe characters.405_gen_nlspath_nlspath=$(printf '%s' "$_gen_nlspath_nlspath" | tr -d '|')406407# Return the result.408printf '%s' "$_gen_nlspath_nlspath"409}410411ALL=0412NOSKIP=1413SKIP=2414415# Filters text out of a file according to the build type.416# @param in File to filter.417# @param out File to write the filtered output to.418# @param type Build type.419filter_text() {420421_filter_text_in="$1"422shift423424_filter_text_out="$1"425shift426427_filter_text_buildtype="$1"428shift429430# Set up some local variables.431_filter_text_status="$ALL"432_filter_text_last_line=""433434# We need to set IFS, so we store it here for restoration later.435_filter_text_ifs="$IFS"436437# Remove the file- that will be generated.438rm -rf "$_filter_text_out"439440# Here is the magic. This loop reads the template line-by-line, and based on441# _filter_text_status, either prints it to the markdown manual or not.442#443# Here is how the template is set up: it is a normal markdown file except444# that there are sections surrounded tags that look like this:445#446# {{ <build_type_list> }}447# ...448# {{ end }}449#450# Those tags mean that whatever build types are found in the451# <build_type_list> get to keep that section. Otherwise, skip.452#453# Obviously, the tag itself and its end are not printed to the markdown454# manual.455while IFS= read -r _filter_text_line; do456457# If we have found an end, reset the status.458if [ "$_filter_text_line" = "{{ end }}" ]; then459460# Some error checking. This helps when editing the templates.461if [ "$_filter_text_status" -eq "$ALL" ]; then462err_exit "{{ end }} tag without corresponding start tag" 2463fi464465_filter_text_status="$ALL"466467# We have found a tag that allows our build type to use it.468elif [ "${_filter_text_line#\{\{* $_filter_text_buildtype *\}\}}" != "$_filter_text_line" ]; then469470# More error checking. We don't want tags nested.471if [ "$_filter_text_status" -ne "$ALL" ]; then472err_exit "start tag nested in start tag" 3473fi474475_filter_text_status="$NOSKIP"476477# We have found a tag that is *not* allowed for our build type.478elif [ "${_filter_text_line#\{\{*\}\}}" != "$_filter_text_line" ]; then479480if [ "$_filter_text_status" -ne "$ALL" ]; then481err_exit "start tag nested in start tag" 3482fi483484_filter_text_status="$SKIP"485486# This is for normal lines. If we are not skipping, print.487else488if [ "$_filter_text_status" -ne "$SKIP" ]; then489if [ "$_filter_text_line" != "$_filter_text_last_line" ]; then490printf '%s\n' "$_filter_text_line" >> "$_filter_text_out"491fi492_filter_text_last_line="$_filter_text_line"493fi494fi495496done < "$_filter_text_in"497498# Reset IFS.499IFS="$_filter_text_ifs"500}501502503