#!/bin/bash
#
# Bash library and executable to configure djibian.
#
# Copyright © 2025 Jean-Jacques Brucker <jjbrucker@foopgp.org>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
# shellcheck disable=SC2013 # To read lines rather than words
# shellcheck disable=SC2034 # variable appears unused
# shellcheck disable=SC2046 # Quote this to prevent word splitting
# shellcheck disable=SC2086 # Double quote warning
# shellcheck disable=SC2128 # expanding array without index warning


if [[ "$1" == --bash-completion ]] ; then
	BL_tmp_a=$("${BASH_SOURCE[0]}" --help | sed -nE 's:^ {2,4}([a-z0-9_]+\>).*:\1:p')
	BL_tmp_o=$(for a in $BL_tmp_a ; do
		echo "[$a]=\"$(eval echo $("$BASH_SOURCE" $a --help | sed -n 's,^  ... \(--[a-z_-]\+[^ ]*\).*,\1,p' | sed 's_<_{_ ; s_|_,_g ; s_>_}_ '))\""
	done)

	eval '_bl_djibian_completion()
	{
		local cur coptions="--help --version"
		local a actions="'$BL_tmp_a'"
		local -A aoptions=('$BL_tmp_o')

		COMPREPLY=()
		cur=${COMP_WORDS[COMP_CWORD]}

		case ${COMP_WORDS[COMP_CWORD-1]} in
			--help|--version)
				# should exit without executing any actions, so do not complete anything
				return 0 ;;
			--frontend)
				COMPREPLY=( $(compgen -W "whiptail dialog zenity NONE" -- $cur ) )
				return 0 ;;
			--add)
				COMPREPLY=( $(compgen -W "$(getent passwd | awk -F ":" "{ if ( \$3 >= 1000 ) print \$1}")" -- $cur ) )
				return 0 ;;
			--remove)
				COMPREPLY=( $(compgen -W "$(getent group sudo | cut -d ":" -f 4 | tr "," " ")" -- $cur ) )
				return 0 ;;
		esac
		case $cur in
			-*)
				if a=$(grep -o "\<\('${BL_tmp_a//$'\n'/\\|}'\)\>" <<< "${COMP_WORDS[@]}") ; then
					COMPREPLY=( $(compgen -W "$coptions ${aoptions[$a]}" -- $cur ) )
					return 0
				fi
				COMPREPLY=( $(compgen -W "$coptions" -- $cur ) )
				return 0 ;;
		esac
		# complete actions if none in ${COMP_WORDS[@]}
		grep -q "\<\('${BL_tmp_a//$'\n'/\\|}'\)\>" <<< "${COMP_WORDS[@]}" || COMPREPLY=( $(compgen -W "$actions" -- $cur ) )
		return 0
	}'
	unset BL_tmp_a BL_tmp_o
	complete -F _bl_djibian_completion "$(basename "$BASH_SOURCE")" "$BASH_SOURCE"
	return 0
fi

# If sourcing while _bl_djibian_parseoptions is already set, execute the function and return without reloading rest of file.
if [[ "$BASH_SOURCE" != "$0" ]] && [[ "$(type -t _bl_djibian_parseoptions)" == function ]] ; then
	_bl_djibian_parseoptions "$@"
	return $?
fi

### Constants ###

BL_DJIBIAN_NAME="$(basename "$(readlink -f "$BASH_SOURCE")" )"
BL_DJIBIAN_VERSION="0.3.5-1"
BL_DJIBIAN_FUNCTIONS=( $(sed -n 's,^\(bl_[^( ]*\) *().*,\1,p' "$BASH_SOURCE") )
readonly BL_DJIBIAN_NAME BL_DJIBIAN_VERSION BL_DJIBIAN_FUNCTIONS

declare -r BL_DJIBIAN_EXTRA_GROUPS="cdrom floppy audio dip video plugdev netdev scanner bluetooth lpadmin"

[[ "$BASH_SOURCE" == "$0" ]] && declare -r BL_DJIBIAN_isprogram=1 || declare -r BL_DJIBIAN_isprogram=0

### Others Globals ###

if ((BL_DJIBIAN_isprogram)) ; then
	TEXTDOMAIN="bashlibs"
	TEXTDOMAINDIR="$(dirname "$(readlink -f "$BASH_SOURCE")" )/../share/locale"
	# Require Bash 5.2. - https://www.kurokatta.org/grumble/2023/11/bash-translated-strings#fnref3
	shopt -s noexpand_translation
fi

BL_DJIBIAN_chelpmsg="
"$"System tools for Djibian GNU/Linux. Mainly to manage users.""

MAIN OPTIONS:
  -f, --frontend PROGRAM   "$"Select a frontend program"" {NONE,whiptail,dialog,zenity} - "$"Environment variable: ""BL_INTERACTIVE_FRONTEND"

### external functions ###

# shellcheck source=bl-interactive
source "$(dirname "$BASH_SOURCE")"/bl-interactive --
# shellcheck source=bl-pgpid
source "$(dirname "$BASH_SOURCE")"/bl-pgpid --
source "$(dirname "$BASH_SOURCE")"/bl-security --

### internal functions ###

_bl_djibian_parseoptions() {
	local npp=$# frontend
	for ((;$#;)) ; do
		case "$1" in
			-f|--frontend) shift; frontend="$1";; # Will be passed to bl-interactive
			-h|--help) printf "%s\n%s%s" "$BL_DJIBIAN_usage" "$BL_DJIBIAN_chelpmsg" "$BL_DJIBIAN_shelpmsg" ; return 1 ;;
			-V|--version) printf "%s %s\n" "$BL_DJIBIAN_NAME" "$BL_DJIBIAN_VERSION" ; return 1 ;;
			--) shift ; break ;;
			-*) printf "%s: unrecognized option '%s'\n\nTry '%s --help' for more information.\n" "$BL_DJIBIAN_NAME" "$1" "$BASH_SOURCE" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done
	BL_DJIBIAN_NOPTIONS=$((npp-$#))

	if [[ "$frontend" ]] ; then
		source "$(dirname "$BASH_SOURCE")"/bl-interactive --frontend "$frontend" --
	fi
}

### public functions / program actions ###

bl_djibian_adduser()
{
	local name
	((BL_DJIBIAN_isprogram)) && name="$BL_DJIBIAN_NAME ${FUNCNAME:11}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS] [user]"
	local helpmsg="
"$"Add user to this djibian system"". "$"Need root permissions"" (sudo).
"$"Missing input will be asked interactively.""

OPTIONS:
  -s, --surname SURNAME            "$"Surname/family name at birth""
  -g, --given-names GIVEN_NAMES    "$"Given names at birth, separated by space ' ' or comma ',' or hyphen '-'.""
  -d, --birth-date BIRTH_DATE      "$"Birth date, expected format : YYYY-MM-DD.""
  -c, --birth-country COUNTRY_CODE "$"3 letters country code of birth place: FRA, GBR, ...""
  -e, --email EMAIL                "$"User email (optional)""
  -t, --phone NUMBER               "$"User phone number (optional)""
  -p, --password PASSWORD          "$"User password (for current djibian Operating System)""
  -P, --passfrom FILE              "$"Get user password from first line of FILE (eg: fifo, tmpfs, /dev/stdin ...)""
  -f, --from-certificate FPR       "$"Get user data from OpenPGP certificate identified by given FINGERPRINT.""
  -F, --from-pgpid-token           "$"Get user data from OpenPGP ID smartcard (eg: yubikey, nitrokey, ...)""
  -v, --verify                     "$"Resume input data for verification""
  -a, --admin                      "$"Enable admin rights (root power through sudo group)""

"$"Please note that --from-pgpid-token and --from-certificate will automaticaly configure user's HOME
for OpenPGP usages, see: ""$BL_DJIBIAN_NAME confhome4pgp --help
"
	local username sname gname bdate bcountry email phone passwd verify="" extragroups fromcard=0 keyfpr
	for ((;$#;)) ; do
		case "$1" in
			-s|--surname)
				shift ; sname=$1 ;;
			-g|--given-names|--givennames)
				shift ; gname=$1 ;;
			-d|--birth-date|birthdate)
				shift ; bdate=$1 ;;
			-c|--birth-country|birthcountry)
				shift ; bcountry=$1 ;;
			-e|--email)
				shift ; email=$1 ;;
			-t|--phone)
				shift ; phone=${1//[^0-9() .+-]/} ;;
			-p|--password)
				shift ; passwd=$1 ;;
			-P|--passfrom|--pass-from)
				shift ; passwd=$( sed 1q < "$1" ) || return 1 ;;
			-f|--from-certificate)
				[[ "${2^^}" =~ ^(0X)?([ABCDEF0-9]{40})$ ]] || { printf "$FUNCNAME: Error: "$"--from-certificate expect a PGP fingerprint (40 xdigits).""\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ; }
				shift ; keyfpr=${BASH_REMATCH[2]} ; fromcard=0 ;;
			-F|--from-pgp-card|--frompgpcard|--from-gpg-card|--fromgpgcard|--from-pgpid-card|--frompgpidcard|--from-pgpid-token) fromcard=1 ; keyfpr="" ;;
			-v|--verify) verify=1 ;;
			-a|--admin) extragroups+=" sudo " ;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) printf "%s %s\n" "$FUNCNAME" "$BL_DJIBIAN_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done

	if [[ "$(id --user)" != 0 ]] ; then
		printf "$FUNCNAME: Error: "$"Need root power (sudo).""\n" >&2
		return 2
	fi

	local identifier uid ret

	if ((fromcard)) ; then
		local -A cardinfo

		su -l "${SUDO_USER:-$USER}" -c "gpgconf --kill scdaemon" || true
		bl_pgpid_token_check --no-fetch --aaname cardinfo || return $?
		gpgconf --kill scdaemon

		email=${cardinfo[pgpid_email]}
		identifier=${cardinfo[pgpid_id]}
		username=${cardinfo[pgpid_name]}
		keyfpr=${cardinfo[pgpid_Skeyfpr]}
	elif [[ "$keyfpr" ]] ; then
		local fprmbox=$(bl_pgpid_get --errexit-g=1 "0x$keyfpr") || return $1
		read keyfpr email identifier <<<"$fprmbox"
	fi

	if [[ "$identifier" ]] ; then
		bdate=$(printf "// "$"substitued by your OpenPGP ID")
		sname=$bdate
		gname=$bdate
		bcountry=$bdate
	fi

	# If username is given as first argument, use it (and override username extracted from smartcard)
	[[ -z "$1" ]] || username="$1"

	[[ "$sname" ]] || sname=$(bl_input $"User surname (family name) at birth")
	[[ "$gname" ]] || gname=$(bl_input $"User forenames (all given names) at birth")
	[[ "$bdate" ]] || bdate=$(bl_input $"User birth date (YYYY-MM-DD)")
	[[ "$bcountry" ]] || bcountry=$(bl_input --default "FRA" $"User birth country (3 letter code)")
	# If no username, guess it from email (optionnal)
	[[ "$username" ]] || username=${email%%@*}
	# If there is still no username, generate one from given names and surname.
	[[ "$username" ]] || username="$(LANG= sed ' s,\b\([a-z]\)[^ ,;-]*,\1,g  ;  s,[^a-z],,g ' <<<"${gname,,}" )$(LANG= sed ' s,[^a-z],, ' <<<"${sname,,}" )"

	if ! ( LANG=C.UTF-8 ; [[ "$username" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]] ) ; then
		username="$(LANG= ; sed 's,^[^a-zA-Z]*,, ; s,[^a-zA-Z0-9_-]*,,g ' <<<"$username" )"
		printf "$FUNCNAME: Notice:
"$" As defined by IEEE Std 1003.1-2001, the username should consist only of
 letters, digits, underscores, periods, at signs and dashes. And it should
 start with a letter.""\n" >&2
		[[ "$username" ]] || return 2
		printf "$FUNCNAME: Notice:
"$" Then username has been changed to: ""$username\n" >&2
	fi

	[[ "$passwd" ]] || passwd=$(bl_new_password)

	while [[ "$verify" ]] ; do
		! bl_yesno --default=no --text \
	"
	"$"Surname at birth:""     ${sname}
	"$"Given names at birth:"" ${gname}
	"$"Date of birth:""        ${bdate}
	"$"Country of birth:""     ${bcountry}
	"$"Email:""                ${email}
	"$"Phone number:""         ${phone}
	"$"User name:""            ${username}
	"$"User Password:""        ${passwd//?/*}
	" \
			$"Is that correct" || break

		sname=$(bl_input --default "${sname^^}" $"Birth surname (family name)")
		gname=$(bl_input --default "${gname}" $"Birth names (all given names)")
		bdate=$(bl_input --default "${bdate}" $"Birth date (YYYY-MM-DD)")
		bcountry=$(bl_input --default "${bcountry}" $"Birth country (3 letter code)")
		email=$(bl_input --email --default "${email}" $"Email")
		phone=$(bl_input --default "${phone}" $"Phone number")
		phone=${phone//[^0-9() .+-]/}
		username=$(bl_input --default "${username}" $"User name (only letters [a-zA-Z], digits [0-9], and [_-])")
		username="$(LANG=C.UTF-8 ; sed 's,^[^a-zA-Z]*,, ; s,[^a-zA-Z0-9_-]*,,g ' <<<"$username" )"
		passwd=$(bl_input --password --default "${passwd}" --text $"User password (for this system)" "Password")
	done
	[[ "$identifier" ]] || identifier="u4$(bl_pgpid_gen_u4 --surname "$sname" --given-names "$gname" --birth-date "$bdate" --birth-country "$bcountry")" || return $?
	uid=$(bl_pgpid_gen_uid -- "${identifier: 2}") || return $?

	local group groups
	printf "$FUNCNAME: Info: "$"Check system groups.""..\n" >&2
	for group in $BL_DJIBIAN_EXTRA_GROUPS $extragroups ; do
		if $(getent group $group > >(sed 's,^, ,' >&2) ) ; then
			groups+="$group,"
		else
			printf " Notice: "$"No %s group."" (getent -> $?)\n" "'$group'" >&2
		fi
	done

	adduser --disabled-password --comment "${username},${bdate//[^0-9]/},,$phone,$email" --home "/home/$identifier" "$username" || return $?
	# groupmod, usermod and passwd are used after "adduser" because these 4 commands are overcomplicated and/or buggy.
	groupmod --gid "$uid" "$username" || { ret=$? ; deluser --stdoutmsglevel=info "$username" ; return $ret ; }
	usermod --append --groups "${groups%,}" --uid "$uid" --gid "$uid" "$username" || { ret=$? ; deluser --stdoutmsglevel=info "$username" ; return $ret ; }
	# following chgrp should be done by usermod, but it doesn't ...
	chgrp --no-dereference --recursive "$username" "/home/$identifier" || { ret=$? ; deluser --stdoutmsglevel=info "$username" ; return $ret ; }

	echo "$passwd" | passwd --stdin "$username" || { ret=$? ; deluser --stdoutmsglevel=info "$username" ; return $ret ; }

	if [[ "$keyfpr" ]] ; then
		su -l "$username" -c "'$(readlink -f $BASH_SOURCE)' confhome4pgp $keyfpr"\
			|| printf "$FUNCNAME: Warning: "$"Unable to configure %s's HOME for OpenPGP"" -"$" See: ""$BASH_SOURCE confhome4pgp\n" "$username" >&2
	fi

	printf "$FUNCNAME: Notice: "$"user %s (%s) successfully added to current system"".\n" "$username" "$identifier" >&2
}

bl_djibian_confhome4pgp()
{
# TODO: manage options like --{,disable,enable}-{systemd,gnupg,ssh,git,face,all} To enable or disable specific parts of configuration
	local name
	((BL_DJIBIAN_isprogram)) && name="$BL_DJIBIAN_NAME ${FUNCNAME:11}" || name="$FUNCNAME"
	local usage="Usage: $name KEYFPR"
	local helpmsg="
"$"Reconfigure HOME directory for djibian's OpenPGP usage:""
- '~/.config/systemd/': "$"Force SSH_AUTH_SOCK to use gpg-agent (crazy systemd workaround).""
- '\$GNUPGHOME':         "$"Set KEYFPR as the GnuPG's default-key.""
- '\$GNUPGHOME':         "$"Set KEYFPR as the only one ultimate key (downgrading other trust ultimate -> full).""
- '~/.ssh':             "$"Set last usable OpenPGP authentication key as the only one authorized key for ssh login.""
- '~/.gitconfig':       "$"Set KEYFPR as the git global « user.signingKey ».""
- '~/.face':            "$"If KEYFPR certificate contain an avatar, copy it to this file.""
"
	local fromcard=0 ret=0
	for ((;$#;)) ; do
		case "$1" in
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) printf "%s %s\n" "$FUNCNAME" "$BL_DJIBIAN_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done

	[[ "${1^^}" =~ ^(0X)?([ABCDEF0-9]{40})$ ]] || { printf "$FUNCNAME: Error: "$"Argument must be a PGP fingerprint (40 xdigits).""\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ; }
	local key keyfpr=${BASH_REMATCH[2]}

	printf "$FUNCNAME: Info: "$"Check directories and %s certificate.""..\n" "$keyfpr" >&2
	mkdir -p "${GNUPGHOME:-$HOME/.gnupg}" ~/.ssh || return $?
	chmod go-rwx "${GNUPGHOME:-$HOME/.gnupg}" ~/.ssh || return $?
	gpg --list-key 0x$keyfpr || gpg --recv-key 0x$keyfpr || return $?

	if [[ -f /usr/lib/systemd/user/gpg-agent.socket ]] ; then
		mkdir --parents ~/.config/systemd/user/sockets.target.wants || return $?
		printf "$FUNCNAME: Info: " >&2
		cp --force --verbose /usr/lib/systemd/user/gpg-agent.socket ~/.config/systemd/user/sockets.target.wants >&2 || return $?
	else
		printf "$FUNCNAME: Notice: "$"%s is missing. "$"Did you get rid of systemd ?""\n" "'/usr/lib/systemd/user/gpg-agent.socket'" >&2
	fi

	printf "$FUNCNAME: Info: "$"Set %s as the GnuPG's default-key"" (${GNUPGHOME:-~/.gnupg})...\n" "$keyfpr" >&2
	echo "default-key:0:\"$keyfpr" | gpgconf --quiet --change-options gpg >&2 || return $?

	printf "$FUNCNAME: Info: "$"Set %s as the only one ultimate key"" (${GNUPGHOME:-~/.gnupg})...\n" "$keyfpr" >&2
	for key in $(gpg --export-ownertrust | awk -F: '/:6:/ {print $1} ') ; do
		gpg --quick-set-ownertrust "$key" full || return $?
	done
	gpg --quick-set-ownertrust "$keyfpr" ultimate || return $?

	printf "$FUNCNAME: Info: "$"Set last usable OpenPGP authentication key as the only one authorized key for ssh login.""..\n" >&2
	gpg --export-ssh-key "$keyfpr" | sed "s,\$, $(date -I) $FUNCNAME $keyfpr," > ~/.ssh/authorized_keys || return $?
	chmod go-rwx ~/.ssh/authorized_keys

	printf "$FUNCNAME: Info: "$"Set KEYFPR as the git global « user.signingKey ».""..\n" >&2
	touch ~/.gitconfig || return $?
	grep --quiet "^\[user\]" ~/.gitconfig || echo -e "\n[user]" >> ~/.gitconfig || return $?
	sed -i '/^\[user\]/,/^\[/ { /^[[:space:]]*signingKey\>/d } ; /^\[user\]/a\\tsigningKey = '"$keyfpr # set $(date -I) by $FUNCNAME"  ~/.gitconfig || return $?

	local avatar=$(bl_pgpid_avatar "$keyfpr" | tail -n 1)
	if [[ -f "$avatar" ]] ; then
		touch ~/.face || return $?
		printf "$FUNCNAME: Info: " >&2
		cp --force --verbose ~/.face ~/.face.$(md5sum ~/.face | head -c 32) >&2 || return $?
		printf "$FUNCNAME: Info: " >&2
		cp --force --verbose "$avatar" ~/.face >&2 || return $?
	fi
}

bl_djibian_admins()
{
	local name
	((BL_DJIBIAN_isprogram)) && name="$BL_DJIBIAN_NAME ${FUNCNAME:11}" || name="$FUNCNAME"
	local usage="Usage: $name [OPTIONS]"
	local helpmsg="
"$"List, add or remove local admins (root power through sudo group). Add or remove need admin rights.""
"$"SUDO_USER is not allowed to remove himself (from sudo group).""

OPTIONS:
  -a, --add USER        "$"Add user to admin group (sudo). May be passed multiple time.""
  -r, --remove USER     "$"Remove user to admin group (sudo). May be passed multiple time.""
  -l, --list            "$"Do nothing, as users with admin rights are always output.""
"
	local toadd toremove ret=0
	for ((;$#;)) ; do
		case "$1" in
			-a|--add)
				shift ; toadd+="$1 " ;;
			-r|--remove)
				shift ; toremove+="$1 " ;;
			-l|--list)
				true ;;
			-h|--help) printf "%s\n%s\n" "$usage" "$helpmsg" ; return ;;
			-V|--version) printf "%s %s\n" "$FUNCNAME" "$BL_DJIBIAN_VERSION" ; return ;;
			--) shift ; break ;;
			-*) printf "$FUNCNAME: Error: "$"Unrecognized option"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$name" >&2 ; return 2 ;;
			*) break ;;
		esac
		shift
	done
	((! $#)) || printf "$FUNCNAME: Warning: "$"Ignoring extra args '%s'...""\n" "$1" >&2

	if [[ "$toadd" ]] || [[ "$toremove" ]] ; then
		if [[ "$(id --user)" != 0 ]] ; then
			printf "$FUNCNAME: Error: "$"Need root power (sudo).""\n" >&2
			return 2
		fi
		local user
		for user in $toremove ; do
			if [[ "$user" != ${SUDO_USER:-$USER} ]] ; then
				gpasswd --delete "$user" sudo >&2 || ((ret+=$?))
			else
				printf "$FUNCNAME: Warning: $user: "$"No one can remove himself from admin group"" (sudo).\n" >&2
				((++ret))
			fi
		done
		for user in $toadd ; do
			gpasswd --add "$user" sudo >&2 || ((ret+=$?))
		done

	fi
	getent group sudo | sed -E ' s,^[^:]*:[^:]*:[^:]*:([^:]*),\1, ; s:,: :g '
	ret=$((ret+${PIPESTATUS[0]}))

	return "$ret"
}

### Init ###

if ((BL_DJIBIAN_isprogram)) ; then
	BL_DJIBIAN_usage="Usage: $BASH_SOURCE [MAIN_OPTIONS]... ACTION [OPTIONS]... [ARGUMENTS]..."
	BL_DJIBIAN_shelpmsg="
  -h, --help               "$"Show help and exit.""
  -V, --version            "$"Show version and exit.""

ACTIONS:
$(for f in "${BL_DJIBIAN_FUNCTIONS[@]}" ; do printf "   %-19s%s\n" "${f:11}" "$($f --help | sed -n '/^$/{n;p;q}')" ; done)

All actions support a --help option, eg:
$ $BASH_SOURCE ${BL_DJIBIAN_FUNCTIONS:11} --help

$BL_DJIBIAN_NAME is also bash library, see:
$ source $BASH_SOURCE --help
"
else
	BL_DJIBIAN_usage="Usage: source $BASH_SOURCE [MAIN_OPTIONS]..."
	BL_DJIBIAN_shelpmsg="
      --bash-completion    set completion for $BASH_SOURCE program and return (without loading anything else)

Functions:
$(for f in "${BL_DJIBIAN_FUNCTIONS[@]}" ; do printf "   %-19s%s\n" "$f" "$($f --help | sed -n '/^$/{n;p;q}')" ; done)

"$"Reminder: when used as a library, all functions calls share the same environment variables, i.e. the same global options.""
"
fi

# Parse Options
_BL_DJIBIAN_RETVAL=0
_bl_djibian_parseoptions "$@" || _BL_DJIBIAN_RETVAL=$?

# Do nothing else if sourced
[[ "$BASH_SOURCE" == "$0" ]] || return $_BL_DJIBIAN_RETVAL

[[ "$_BL_DJIBIAN_RETVAL" != "1"  ]] || exit 0
[[ "$_BL_DJIBIAN_RETVAL" < 2 ]] || exit $_BL_DJIBIAN_RETVAL

### Run ###
set -e
shift $BL_DJIBIAN_NOPTIONS
if function=$(grep -o "\<bl_djibian_$1\>" <<< "${BL_DJIBIAN_FUNCTIONS[@]}") ; then
	shift
	$function "$@"
	exit $?
else
	printf "$BL_DJIBIAN_NAME: Error: "$"Unrecognized action"" '$1'.\n\n"$"Try '%s --help' for more information"".\n" "$BASH_SOURCE" >&2
	exit 2
fi

