~q3cpma/misc-tools

7322f8048d205ff1e2cd3af871df791172f21d41 — q3cpma 7 months ago 313cd57
Sync posix-build
genhtab:
	Define the header guard based on the search function name
3 files changed, 315 insertions(+), 210 deletions(-)

M build.sh
M build_util.sh
M genhtab.c
M build.sh => build.sh +10 -9
@@ 17,7 17,6 @@ fi

# Get configuration from the environment
CC=${CC:-c99}
STD=${STD:-c99}
CFLAGS=${CFLAGS:-}
CPPFLAGS=${CPPFLAGS:-}
append_cppflag -D_DEFAULT_SOURCE


@@ 29,17 28,19 @@ PGO=${PGO:-false}
STATIC=${STATIC:-false}
NATIVE=${NATIVE:-false}
CONFIG=${CONFIG:-release}
DESTDIR=${DESTDIR+${DESTDIR%/}}
PREFIX=$(mkdir -p "$DESTDIR${PREFIX:-/usr/local}"; readlinkf "$DESTDIR${PREFIX:-/usr/local}")
PREFIX=${PREFIX%/}
PREFIX=$(preadlinkf "${DESTDIR+${DESTDIR%/}}${PREFIX:-/usr/local}")

typecheck bool LTO PGO STATIC NATIVE
typecheck uint JOBS

# Target specific definitions
NAME=$(basename -- "$BIN")
gitver=$(git_version ..)
STD=c99
[ -f VERSION ] && version=$(cat ../VERSION) || version=$(git_version ..)
append_cppflag \
	-DPROG_NAME="$(dquote "$NAME" 2)" \
	-DPROG_VERSION="$(dquote "$gitver" 2)"
	-DPROG_NAME="$(dquote "$BIN" 2)" \
	-DPROG_VERSION="$(dquote "$version" 2)"

# Prepare source list
case "$BIN" in
	htmlencode|urldecode|urlencode|wcswidth|genhtab)
		SRC="$BIN.c misc.c"


@@ 70,7 71,7 @@ else
				pb_install -m 755 "$PREFIX"/bin/ "$BIN"
				;;
			uninstall)
				uninstall -- "$PREFIX/bin/$NAME"
				uninstall "$PREFIX"/bin/"$BIN"
				;;
			help)
				pb_usage 0

M build_util.sh => build_util.sh +293 -198
@@ 1,30 1,131 @@
# Various utilities for other POSIX sh scripts
#
# Portability:
#     xargsnl: GNU, *BSD, MacOS, Illumos
#
# +----------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# | cmd/OS   | GNU | OpenBSD | NetBSD | DragonflyBSD | FreeBSD | MacOS | Illumos | HP-UX | AIX | IRIX | Tru64 |
# +----------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# | xargs -0 | o   | o       | o      | o            | o       | o     | o       |       |     |      |       |
# +----------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
set -u

# Check if local or typeset is available
if ! sh -c 'fun() { local a=1; }; fun'
then
	if sh -c 'fun() { typeset a=1; }; fun'
	then
		alias local=typeset
	else
		echo "local/typeset not supported by sh, aborting"
		exit 1
	fi
fi

[ "${BASH:-}" ] && shopt -s expand_aliases


alias ffmpeg='ffmpeg -hide_banner'
alias ffprobe='ffprobe -hide_banner'

# Portable echo, without any option
pecho()
{
	printf '%s\n' "$*"
}

# Print all arguments prefixed by the program name to stderr and exits with
# status 1
# Print all arguments to stderr and exits with status 1
die()
{
	pecho "$@" >&2
	exit 1
}

# Check if the variable named $1 exists
isset()
{
	eval "[ \"\${$1+x}\" ]"
}

# Return the value of variable named $1, fails if no such variable exists
value()
{
	match "$1" '[0-9]+' &&
		{ pecho "positional parameters can't be used with value()" >&2; return 1; }
	isset "$1" ||
		{ pecho "$var: variable not set" >&2; return 1; }
	eval pecho "\"\$$1\""
}

# Append to variable $1, which need not exists
append()
{
	local var=$1
	shift
	eval "$var=\${$var:+\$$var }\$*"
}

# Append to variable $1 as a newline delimited list (with a newline at the end)
# which needs not exists
appendnl()
{
	local var=$1
	shift
	eval "$var=\${$var:+\$$var }\$(printf '%s\n' \"\$@\")"
}

# Return its last argument
lastarg()
{
	eval pecho "\${$#}"
}

# Check that type $1 is respected for the remaining argument variables
typecheck()
{
	local type=$1 var= val= success=true
	shift
	for var
	do
		match "$var" '[0-9]+' && die "positional parameters can't be used with typecheck"
		val=$(value "$var") || exit 1
		case "$type" in
			bool)
				[ "$val" != true ] && [ "$val" != false ] && {
					pecho "$var is of type bool, must contain \"true\" or \"false\", not \"$val\"" >&2
					success=false
				}
				;;
			int)
				! match "$val" '[+-]?[0-9]+' && {
					pecho "$var is of type int, must contain an integer, not \"$val\"" >&2
					success=false
				}
				;;
			uint)
				! match "$val" '\+?[0-9]+' && {
					pecho "$var is of type uint, must contain a positive integer, not \"$val\"" >&2
					success=false
				}
				;;
			float)
				{ ! match "$val" '[+-]?[0-9]*\.[0-9]*' || match "$val" '[+-]?\.'; } && {
					pecho "$var is of type float, must contain a floating point value, not \"$val\"" >&2
					success=false
				}
				;;
			*)
				die "$type: unknown type"
				;;
		esac
	done
	$success
}

# Like atexit(3), a way to stack EXIT traps
# Caution: overwrite the traps for terminating signals
atexit()
{
    if [ "${_atexit_scripts:-}" ]
    then
        _atexit_scripts="$_atexit_scripts; $1"
    else
        _atexit_scripts=$1
		trap 'exit 1' HUP INT QUIT ABRT ALRM TERM
		trap 'eval "$_atexit_scripts"' EXIT
	fi
}

# Returns 0 if $1 matches $2 (as an ERE), 1 otherwise
match()
{


@@ 35,61 136,57 @@ match()
# remaining argument paths or if one of these doesn't exists
requirefile()
{
	_testarg=$1
	local testarg=$1 i=
	shift
	for _i
	for i
	do
		[ ! -L "$_i" ] && [ ! -e "$_i" ] && die "$_i: file not found"
		if [ "$_testarg" != "-e" ] && [ ! "$_testarg" "$_i" ]
		[ ! -L "$i" ] && [ ! -e "$i" ] && die "$i: file not found"
		if [ "$testarg" != "-e" ] && [ ! "$testarg" "$i" ]
		then
			case "$_testarg" in
				-b)    die "$_i: not a block device";;
				-c)    die "$_i: not a character special file";;
				-d)    die "$_i: not a directory";;
				-f)    die "$_i: not a regular file";;
				-g)    die "$_i: not a setgid file";;
				-h|-L) die "$_i: not a symbolic link";;
				-p)    die "$_i: not a FIFO";;
				-r)    die "$_i: not a readable file";;
				-S)    die "$_i: not a socket";;
				-s)    die "$_i: not a non-empty file";;
				-u)    die "$_i: not a setuid file";;
				-w)    die "$_i: not a writeable file";;
				-x)    die "$_i: not a executable file";;
			case "$testarg" in
				-b)    die "$i: not a block device";;
				-c)    die "$i: not a character special file";;
				-d)    die "$i: not a directory";;
				-f)    die "$i: not a regular file";;
				-g)    die "$i: not a setgid file";;
				-h|-L) die "$i: not a symbolic link";;
				-p)    die "$i: not a FIFO";;
				-r)    die "$i: not a readable file";;
				-S)    die "$i: not a socket";;
				-s)    die "$i: not a non-empty file";;
				-u)    die "$i: not a setuid file";;
				-w)    die "$i: not a writeable file";;
				-x)    die "$i: not a executable file";;
			esac
		fi
	done
}

checkbin()
{
	command -v "$1" >/dev/null
}

# For each argument, check for executable presence; use a,b,c... for alternatives
requirebin()
{
	for _i
	local i= j= success=
	for i
	do
		if match "$_i" '.*|.*'
		if match "$i" '.*|.*'
		then
			_i=$(pecho "$_i" | tr ',' ' ')
			_success=false
			for _j in $_i
			i=$(pecho "$i" | tr ',' ' ')
			success=false
			for j in $i
			do
				if checkbin "$_j"
				if command -v "$j" >/dev/null
				then
					_success=true
					success=true
				fi
			done
			if ! $_success
			if ! $success
			then
				pecho "At least one executable amongst `$_i` required"
				pecho "At least one executable amongst `$i` required"
				return 1
			fi
		elif ! checkbin "$_i"
		elif ! command -v "$i" >/dev/null
		then
			pecho "$_i: executable not found"
			pecho "$i: executable not found"
			return 1
		fi
	done


@@ 98,11 195,12 @@ requirebin()
# Die with an appropriate message if one of the argument paths exists
forbidfile()
{
	for _i
	local i=
	for i
	do
		if [ -L "$_i" ] || [ -e "$_i" ]
		if [ -L "$i" ] || [ -e "$i" ]
		then
			die "$_i: file already exists"
			die "$i: file already exists"
		fi
	done
}


@@ 128,14 226,13 @@ quote()
# Double quote $1 $2 times (defaults to 1)
dquote()
{
	_cnt=${2:-1}
	_res=$1
	while [ $_cnt -ne 0 ]
	local res=$1 cnt=${2:-1}
	while [ $cnt -ne 0 ]
	do
		_res=$(pecho "$_res" | sed 's#"#\\"#g; s#^#"#; s#$#"#')
		_cnt=$((_cnt - 1))
		res=$(pecho "$res" | sed 's#"#\\"#g; s#^#"#; s#$#"#')
		cnt=$((cnt - 1))
	done
	pecho "$_res"
	pecho "$res"
}

# Escape all BRE metacharacters in $1


@@ 166,13 263,18 @@ glob_escape()
# arguments are passed to find.
listfiles()
{
	_dir=$1
	local dir=$1
	shift
	_esc=$(glob_escape "$_dir")
	find -- "$_dir" \( ! -path "$_esc" -prune \) "$@"
	find -- "$dir" \( ! -path "$(glob_escape "$dir")" -prune \) "$@"
}

# xargs -d'\n' for *BSD/GNU/Solaris
# Portability: GNU, *BSD, MacOS, Illumos
# +----------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# | cmd/OS   | GNU | OpenBSD | NetBSD | DragonflyBSD | FreeBSD | MacOS | Illumos | HP-UX | AIX | IRIX | Tru64 |
# +----------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# | xargs -0 | o   | o       | o      | o            | o       | o     | o       |       |     |      |       |
# +----------+-----+---------+--------+--------------+---------+-------+---------+-------+-----+------+-------+
# xargs -d'\n' emulation
xargsnl()
{
	tr '\n' '\000' | xargs -0 "$@"


@@ 188,29 290,25 @@ tolower()
headneg()
{
	! match "$1" '[[:digit:]]+' && return 1
	awk -varg=$1 '{if(NR > arg) print buf[NR % arg]; buf[NR % arg] = $0}'
	awk -varg="$1" '{if(NR > arg) print buf[NR % arg]; buf[NR % arg] = $0}'
}

# Format stdin so that __foo_bar__ is underlined and **foo*bar** is emboldened
text_format()
{
	local sgr0= bol= smul= rmul=
	if [ -t 1 ]
	then
		set +e
		_sgr0=$(tput sgr0)
		_bold=$(tput bold)
		_smul=$(tput smul)
		_rmul=$(tput rmul)
		sgr0=$(tput sgr0)
		bold=$(tput bold)
		smul=$(tput smul)
		rmul=$(tput rmul)
		set -e
	else
		_sgr0=
		_bold=
		_smul=
		_rmul=
	fi
	sed -E \
		-e ":a; s#([^_]|^)__(([^_]*(_[^_])*[^_]*)+)__([^_]|\$)#\1$_smul\2$_rmul\5#; ta" \
		-e ":b; s#([^*]|^)\*\*(([^\*]*(\*[^*])*[^*]*)+)\*\*([^*]|\$)#\1$_bold\2$_sgr0\5#; tb"
		-e ":a; s#([^_]|^)__(([^_]*(_[^_])*[^_]*)+)__([^_]|\$)#\1$smul\2$rmul\5#; ta" \
		-e ":b; s#([^*]|^)\*\*(([^\*]*(\*[^*])*[^*]*)+)\*\*([^*]|\$)#\1$bold\2$sgr0\5#; tb"
}

# Extract the extension of a filename (only works for the last extension,


@@ 229,13 327,13 @@ mimetype()
# Extract the basename of a filename (minus the extension)
file_base()
{
	_tmp=$(basename -- "$1")
	_base=${_tmp%.*}
	if [ ! "$_base" ]
	local tmp=$(basename -- "$1")
	local base=${tmp%.*}
	if [ ! "$base" ]
	then
		pecho "$_tmp"
		pecho "$tmp"
	else
		pecho "$_base"
		pecho "$base"
	fi
}



@@ 246,7 344,7 @@ read_password()
	stty -echo
	read -r "$2"
	stty echo
	pecho
	echo
}

# Takes argv ("$@") as argument and determines if the help option is called


@@ 258,8 356,8 @@ is_help()
# Print stdin lines as a pretty list: "line1, line2, ..."
list_join()
{
	_sep=${1:-, }
	sed ':a; N; $!ba; s#\n#'"$(sed_repl_escape "$_sep")"'#g'
	local sep=${1:-, }
	sed ':a; N; $!ba; s#\n#'"$(sed_repl_escape "$sep")"'#g'
}

# Execute $@ with sh then outputs the number of seconds it took


@@ 269,7 367,7 @@ ptime()
		sed -n 's#^real ##p'
}

# prints a random unsigned number of $1 bytes
# Prints a random unsigned number of $1 bytes
rand()
{
	requirefile -c /dev/urandom


@@ 279,10 377,10 @@ rand()
# IFS split $1 into the variables $2, $3, ...
read_split()
{
	_str=$1
	local str=$1
	shift
	read -r "$@" <<-EOF
		$_str
		$str
EOF
}



@@ 290,19 388,19 @@ EOF
# printed to stdout
pselect()
{
	_in=$(cat)
	_len=$(pecho "$_in" | wc -l)
	local choice= in=$(cat)
	local len=$(pecho "$in" | wc -l)
	while true
	do
		pecho "$_in" | awk '{printf "%d) %s\n", NR, $0}' >&2
		pecho "$in" | awk '{printf "%d) %s\n", NR, $0}' >&2
		printf '\n#?' >&2
		read -r _choice </dev/tty
		if ! match "$_choice" '[0-9]+' || [ $_choice -gt $_len ]
		read -r choice </dev/tty
		if ! match "$choice" '[0-9]+' || [ $choice -gt $len ]
		then
			printf '%s: invalid value\n' "$_choice" >&2
			printf '%s: invalid value\n' "$choice" >&2
			continue
		fi
	    pecho "$_in" | sed -n "$_choice"'{p; q}'
	    pecho "$in" | sed -n "$choice"'{p; q}'
		break
	done
}


@@ 320,7 418,7 @@ parallel()
}

# Like the usual tac(1), print lines in reverse
tac()
ptac()
{
	awk '{line[NR] = $0} END {for (i = NR; i; --i) {print line[i]}}' "$@"
}


@@ 328,6 426,7 @@ tac()
# Recursively delete empty directories
rmdir_recursive()
{
	local i=
	for i
	do
		find -- "$i" -type d | awk -F/ '{print NF, $0}' | sort -k1 -n | rev | \


@@ 336,26 435,25 @@ rmdir_recursive()
}

# Simple portable seq without fancy options (use list_join to get -s)
seq()
pseq()
{
	_incr=1
	_first=1
	local incr=1 first=1
	case $# in
		1)  _last=$1;;
		1)  last=$1;;
		2)
			_first=$1
			_last=$2
			first=$1
			last=$2
			;;
		3)
			_first=$1
			_incr=$2
			_last=$3
			first=$1
			incr=$2
			last=$3
			;;
	esac
	while [ $_first -le $_last ]
	while [ $first -le $last ]
	do
		pecho "$_first"
		_first=$((_first + _incr))
		pecho $first
		first=$((first + incr))
	done
}



@@ 371,39 469,49 @@ numcpu()
	getconf _NPROCESSORS_ONLN
}

# Simple readlink
preadlink()
{
	local i= lsout=
	for i
	do
		lsout=$(ls -l -- "$i")
		pecho "${lsout#*"$i -> "}"
	done
}

if command readlink --version 2>/dev/null | grep -qF '^readlink (GNU coreutils)'
then
	alias readlinkf='readlink -f --'
elif checkbin greadlink
	alias preadlinkf='readlink -f --'
elif command -v greadlink >/dev/null
then
	alias readlinkf='greadlink -f --'
	alias preadlinkf='greadlink -f --'
else
	# Portable readlink -f using GNU's semantics (last component need not exist)
	# Currently passes the tests from https://github.com/ko1nksm/readlinkf
	# Currently passes the tests from https://github.com/ko1nksm/preadlinkf
	# except loop detection (`getconf SYMLOOP_MAX` is undefined here, anyway)
	readlinkf()
	preadlinkf()
	{
		[ $# -eq 0 ] && return 1
		_status=0
		_pwd=$(pwd -P)
		for _i
		local i= status=0 pwd=$(pwd -P) base= dir=
		for i
		do
			! [ "$_i" ] && { _status=1; continue; }
			if [ -d "$_i" ]
			! [ "$i" ] && { status=1; continue; }
			if [ -d "$i" ]
			then
				CDPATH= cd -P -- "$_i" >/dev/null 2>&1 || return 1
				CDPATH= cd -P -- "$i" >/dev/null 2>&1 || return 1
				pwd -P
			else
				case "$_i" in */) [ -e "${_i%/}" ] && return 1;; esac
				case "$i" in */) [ -e "${i%/}" ] && return 1;; esac
				while true
				do
					CDPATH= cd -P -- "$(dirname -- "$_i")" >/dev/null 2>&1 || return 1
					base=${_i%/}
					CDPATH= cd -P -- "$(dirname -- "$i")" >/dev/null 2>&1 || return 1
					base=${i%/}
					base=${base##*/}
					if [ -L "$base" ]
					then
						_i=$(ls -l -- "$base")
						_i=${_i#*"$base -> "}
						i=$(ls -l -- "$base")
						i=${i#*"$base -> "}
					else
						dir=$(pwd -P)
						dir=${dir%/}


@@ 412,9 520,9 @@ else
					fi
				done
			fi
			cd -- "$_pwd"
			cd -- "$pwd"
		done
		return $_status
		return $status
	}
fi



@@ 425,7 533,7 @@ pmktemp()
		0)  pecho "mkstemp(${TMPDIR:-/tmp}/tmp.XXXXXX)" | m4;;
		1)  pecho "mkstemp($1)" | m4;;
		3)
		    [ "$1" != -p ] && echo "Usage: pmktemp [-p TMPDIR] [TEMPLATE]" >&2 && return 1
		    ! [ "$1" == -p ] && { pecho "Usage: pmktemp [-p TMPDIR] [TEMPLATE]" >&2; return 1; }
		    pecho "mkstemp($2/$3)" | m4
		    ;;
		*)  return 1;;


@@ 436,37 544,26 @@ pmktemp()
                       # posix-build specific functions #
                       ##################################

# Like atexit(3), a way to stack EXIT traps
_atexit_cmd=
atexit_eval()
{
	eval "$_atexit_cmd"
}
atexit()
{
	[ "$_atexit_cmd" ] && _atexit_cmd="$_atexit_cmd; $1" || _atexit_cmd=$1
}
trap 'exit 1' HUP INT QUIT ABRT ALRM TERM
trap 'atexit_eval' EXIT

# Like rm, but echo what's going on too
rmv()
{
	pecho rm $@
	rm $@
	pecho rm "$@"
	rm "$@"
}

# Call before rewrite_paths() to setup said paths
set_rpaths()
{
	local i= key= val=
	_rpath_script=$(pmktemp)
	atexit 'rm -- "$_rpath_script"'
	for i
	do
		_key=$(bre_escape "rpath_${i%%=*}")
		_val=${i#*=}
		_val=$(sed_repl_escape "$(quote "${_val%/}")")
		pecho "s#^$_key=.*#$_key=$_val#"
		key=$(bre_escape "rpath_${i%%=*}")
		val=${i#*=}
		val=$(sed_repl_escape "$(quote "${val%/}")")
		pecho "s#^$key=.*#$key=$val#"
	done >"$_rpath_script"
}



@@ 481,13 578,13 @@ cp_rewrite_rpaths()
# Fetch URL $1 into path $2 (optional)
pb_fetch()
{
	if checkbin curl
	if command -v curl >/dev/null
	then
		curl -OL ${2:+-o "$2"} -- "$1"
	elif checkbin wget
	elif command -v wget >/dev/null
	then
		wget ${2:+-O "$2"} -- "$1"
	elif checkbin fetch
	elif command -v fetch >/dev/null
	then
		fetch ${2:+-o "$2"} "$1"
	else


@@ 524,24 621,23 @@ EOF
# See above
pb_install()
{
	_mode=
	_rpath=false
	_quiet=false
	local mode= rpath=false quiet=false dest= destdir= i=
	while getopts "m:rq" OPT
	do
		case "$OPT" in
			m)  _mode=$OPTARG;;
			r)  _rpath=true;;
			q)  _quiet=true;;
			m)  mode=$OPTARG;;
			r)  rpath=true;;
			q)  quiet=true;;
			\?) pb_install_usage;;
		esac
	done
	shift $((OPTIND - 1))
	[ $# -lt 2 ] && pb_install_usage
	_dest=$1
	dest=$1
	shift

	# Check filetypes for FILE...
	i=
	for i
	do
		[ ! -L "$i" ] && [ ! -e "$i" ] && die "$i: file not found"


@@ 549,41 645,41 @@ pb_install()
	done

	# Create destination if needed
	if [ "${_dest%/}" != "$_dest" ]
	if match "$dest" '.*/'
	then
		_dest=${_dest%/}
		mkdir -p -- "$_dest"
		dest=${dest%/}
		mkdir -p -- "$dest"
	else
		[ $# -gt 1 ] && die "Target $_dest must be a directory"
		mkdir -p -- "$(dirname -- "$_dest")"
		[ $# -gt 1 ] && die "Target $dest must be a directory"
		mkdir -p -- "$(dirname -- "$dest")"
	fi

	# The actual copy with rpath rewriting and chmod
	if [ ! -e "$_dest" ] || [ -f "$_dest" ]
	if [ ! -e "$dest" ] || [ -f "$dest" ]
	then
		[ $# -gt 1 ] && die "Target $_dest must be a directory"
		! "$_quiet" && pecho "Installing $1 to $_dest"
		if "$_rpath"
		[ $# -gt 1 ] && die "Target $dest must be a directory"
		! $quiet && pecho "Installing $1 to $dest"
		if $rpath
		then
			cp_rewrite_rpaths "$1" "$_dest"
			cp_rewrite_rpaths "$1" "$dest"
		else
			cp -- "$1" "$_dest"
			cp -- "$1" "$dest"
		fi
		[ "$_mode" ] && chmod "$_mode" -- "$_dest"
	elif [ -d "$_dest" ]
		[ "$mode" ] && chmod "$mode" -- "$dest"
	elif [ -d "$dest" ]
	then
		_destdir=$_dest
		destdir=$dest
		for i
		do
			_dest=$_destdir/$(basename -- "$i")
			! "$_quiet" && pecho "Installing $i into $_dest"
			if "$_rpath"
			dest=$destdir/$(basename -- "$i")
			! $quiet && pecho "Installing $i into $dest"
			if $rpath
			then
				cp_rewrite_rpaths "$i" "$_dest"
				cp_rewrite_rpaths "$i" "$dest"
			else
				cp -- "$i" "$_dest"
				cp -- "$i" "$dest"
			fi
			[ "$_mode" ] && chmod "$_mode" -- "$_dest"
			[ "$mode" ] && chmod "$mode" -- "$dest"
		done
	fi
}


@@ 591,11 687,12 @@ pb_install()
# remove files and empty directories leftover
uninstall()
{
	local i=
	for i
	do
		rm -- "$i"
		set +e
		rmdir -p -- "$(dirname -- "$i")" >/dev/null
		rmdir -p -- "$(dirname -- "$i")" 2>/dev/null
		set -e
	done
}


@@ 604,29 701,29 @@ uninstall()
# root directory
git_version()
{
	_repo_root=${1%/}
	local repo_root=${1%/} status= ref=
	if command -v git >/dev/null
	then
		set +e
		git rev-parse --verify --quiet HEAD
		_status=$?
		status=$?
		set -e
	else
		if [ ! -f "$_repo_root"/.git/HEAD ]
		if [ ! -f "$repo_root"/.git/HEAD ]
		then
			_status=1
			status=1
		else
			_ref=$(cut -d' ' -f2 "$_repo_root"/.git/HEAD)
			if [ ! -f "$_ref" ]
			ref=$(cut -d' ' -f2 "$repo_root"/.git/HEAD)
			if [ ! -f "$ref" ]
			then
				_status=1
				status=1
			else
				_status=0
				cat -- "$1"/.git/"$_ref"
				status=0
				cat -- "$1"/.git/"$ref"
			fi
		fi
	fi
	if [ "$_status" -ne 0 ]
	if [ $status -ne 0 ]
	then
		pecho "No git version available"
	fi


@@ 715,6 812,7 @@ src2obj()
# Also remove "$@"
cclean()
{
	local i=
	cd -- "$(dirname -- "$BIN")"
	listfiles . -name '*.gperf' -o -name '*.lex' | \
		sed 's#\.lex$#.c#; s#\.gperf$#.c#' | while IFS= read -r i


@@ 733,18 831,16 @@ cclean()
# Compiler flag testing functions
cctest_setup()
{
	_sampledir=$(mktemp -d)
	_samplec=$_sampledir/sample.c
	_sampleo=$_sampledir/sample.o
	atexit 'rm -r -- "$_sampledir"'
	pecho "int main(void){}" >"$_samplec"
	"$CC" -c -o "$_sampleo" "$_samplec"
	_cctest_dir=$(mktemp -d)
	atexit 'rm -r -- "$_cctest_dir"'
	pecho 'int main(void){}' >"$_cctest_dir"/a.c
	"$CC" -c -o "$_cctest_dir"/a.o "$_cctest_dir"/a.c
}

test_cflag()
{
	[ ! "${_samplec:-}" ] && cctest_setup
	"$CC" -c -o /dev/null "$@" "$_samplec" >/dev/null 2>&1
	! [ "${_cctest_dir:-}" ] && cctest_setup
	"$CC" -c -o /dev/null "$@" "$_cctest_dir"/a.c >/dev/null 2>&1
}
xtest_cflag()
{


@@ 752,7 848,7 @@ xtest_cflag()
}
append_cflag()
{
	CFLAGS="${CFLAGS:-} $*"
	append CFLAGS "$@"
}

test_append_cflag()


@@ 766,8 862,8 @@ xtest_append_cflag()

test_ldflag()
{
	[ ! "${_sampleo:-}" ] && cctest_setup
	"$CC" -o /dev/null "$@" "$_sampleo" >/dev/null 2>&1
	! [ "${_cctest_dir:-}" ] && cctest_setup
	"$CC" -o /dev/null "$@" "$_cctest_dir"/a.o >/dev/null 2>&1
}

xtest_ldflag()


@@ 777,7 873,7 @@ xtest_ldflag()

append_ldflag()
{
	LDFLAGS="${LDFLAGS:-} $*"
	append LDFLAGS "$@"
}

test_append_ldflag()


@@ 792,7 888,7 @@ xtest_append_ldflag()

append_cppflag()
{
	CPPFLAGS="${CPPFLAGS:-} $*"
	append CPPFLAGS "$@"
}

# Make wrapper for C projects, only mandatory variables are SRC as


@@ 804,15 900,14 @@ append_cppflag()
# $@ (except $1 if it is -) is passed as arguments to make
pb_make()
{
	_append_stdin=false
	local append_stdin=false
	if [ $# -ge 1 ] && [ "$1" = - ]
	then
		_append_stdin=true
		append_stdin=true
		shift
	fi
	[ "${JOBS:-}" = 1 ] && JOBS=
	{ cat <<'EOF'; $_append_stdin && cat; } | \
		"${MAKE:-make}" ${JOBS:+-j $JOBS} -f - \
	{ cat <<'EOF'; $append_stdin && cat; } | \
		"${MAKE:-make}" $([ "${JOBS:-}" -ne 1 ] && pecho -j$JOBS) -f - \
		OBJ="$(src2obj)" CC="$CC" BIN="$BIN" CFLAGS="${CFLAGS:-}" CPPFLAGS="${CPPFLAGS:-}" \
		LDFLAGS="${LDFLAGS:-}" LDLIBS="${LDLIBS:-}" "$@"
.POSIX:

M genhtab.c => genhtab.c +12 -3
@@ 1,3 1,4 @@
#include <ctype.h>
#include <getopt.h>
#include <inttypes.h>
#include <stdbool.h>


@@ 369,14 370,22 @@ int main(int argc, char **argv)
		rettype, searchfun, notfoundret, (literal_mode ? "" : "&"), notfoundret);
	xfclose(source);

	char *header_guard;
	pasprintf(&header_guard, "%s_GUARD", searchfun);
	for (size_t i = 0; i < strlen(searchfun); ++i)
	{
		header_guard[i] = toupper(searchfun[i]);
	}
	FILE *header = xfopen(pathbuf, "w");
	fprintf(header,
		"#ifndef GEN_HTAB_GUARD\n"
		"#define GEN_HTAB_GUARD\n"
		"#ifndef %s\n"
		"#define %s\n"
		"\n"
		"#include <stddef.h>\n"
		"#include <stdint.h>\n"
		"\n");
		"\n",
		header_guard,
		header_guard);
	for (size_t i = 0; i < GARRAY_SIZE(addinclude); ++i)
	{
		fprintf(header, "#include \"%s\"\n", addinclude[i]);