#!/usr/bin/env bash
# librefetch
#
# Copyright (C) 2013-2018 Luke Shumaker <lukeshu@parabola.nu>
#
# For just the create_signature() function:
#   Copyright (C) 2006-2013 Pacman Development Team <pacman-dev@archlinux.org>
#   Copyright (C) 2002-2006 Judd Vinet <jvinet@zeroflux.org>
#   Copyright (C) 2005 Aurelien Foret <orelien@chez.com>
#   Copyright (C) 2006 Miklos Vajna <vmiklos@frugalware.org>
#   Copyright (C) 2005 Christian Hamar <krics@linuxforum.hu>
#   Copyright (C) 2006 Alex Smith <alex@alex-smith.me.uk>
#   Copyright (C) 2006 Andras Voroskoi <voroskoi@frugalware.org>
#
# License: GNU GPLv3+
#
# This file is part of LibreFetch.
#
# LibreFetch is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# LibreFetch is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LibreFetch. If not, see <http://www.gnu.org/licenses/>.

# create_signature() is taken from pacman:makepkg, which is GPLv2+,
# so we take the '+' to combine it with our GPLv3+.

. "$(librelib conf)"
. "$(librelib messages)"
setup_traps

tmpfiles=()
tmpdirs=()
trap 'rm -f -- "${tmpfiles[@]}"; rm -rf -- "${tmpdirs[@]}"' EXIT

cmd=${0##*/}
usage() {
	print "Usage: %s [OPTIONS] SOURCE_URL [OUTPUT_FILE]" "$cmd"
	print "Usage: %s -[g|S|M|h]" "$cmd"
	print "Downloads or creates a liberated source tarball."
	echo
	prose "The default mode is to create OUTPUT_FILE, first by trying
	       download mode, then create mode."
	echo
	prose "If OUTPUT_FILE isn't specified, it defaults to the non-directory
	       part of SOURCE_URL, in the current directory."
	echo
	prose "Unless '-C' is specified, if SOURCE_URL does not begin with a
	       configured mirror, create mode is inhibited."
	echo
	prose "In download mode, it simply tries to download SOURCE_URL.  At the
	       beginning of a URL, 'libre://' expands to the first configured
	       mirror."
	echo
	prose "In create mode, it either looks at a build script and uses that
	       to create the source tarball, or it uses GPG to create a
	       signature (if OUTPUT_FILE ends with \`.sig\` or \`.sig.part\`).
	       If it is using GPG to create a signature, but the file that it is
	       trying to sign doesn't exist yet, it recurses on itself to first
	       create that file.  SOURCE_URL is ignored, except that it is used
	       to set the default value of OUTPUT_FILE, and that it may be used
	       when recursing."
	echo
	prose "The default build script is 'PKGBUILD', or 'SRCBUILD' if it
	       exists."
	echo
	prose "Other options, if they are valid \`makepkg\` options, are passed
	       straight to makepkg."
	echo
	print "Example usage:"
	print '  $ %s https://repo.parabola.nu/other/mypackage/mypackage-1.0.tar.gz' "$cmd"
	echo
	print "Options:"
	flag 'Settings:' \
	     "-C"               "Force create mode (don't download)" \
	     "-D"               "Force download mode (don't create)" \
	     "-p <$(_ FILE)>"   "Use an alternate build script (instead of
	                         'PKGBUILD').  If an SRCBUILD exists in the same
	                         directory, it is used instead" \
	     'Alternate modes:' \
	     "-g, --geninteg"   "Generate integrity checks for source files" \
	     "-S, --srcbuild"   "Print the effective build script (SRCBUILD)" \
	     "-M, --makepkg"    "Generate and print the location of the
	                         effective makepkg script" \
	     "-h, --help"       "Show this message"
}

main() {
	BUILDFILE="$(realpath -Lm PKGBUILD)"
	makepkg_opts=()
	extra_opts=()
	mode=download-create
	if ! parse_options "$@"; then
		usage >&2
		exit $EXIT_INVALIDARGUMENT
	fi

	doit
}

doit() {
	# Mode: help ###########################################################

	if [[ $mode =~ help ]]; then
		usage
		exit $EXIT_SUCCESS
	fi

	########################################################################

	makepkg="$(modified_makepkg)"

	# Mode: makepkg ########################################################

	if [[ $mode =~ makepkg ]]; then
		printf '%s\n' "$makepkg"
		exit $EXIT_SUCCESS
	else
		tmpdirs+=("${makepkg%/*}")
	fi

	########################################################################

	local BUILDFILEDIR="${BUILDFILE%/*}"
	if [[ -f "${BUILDFILEDIR}/SRCBUILD" ]]; then
		BUILDFILE="${BUILDFILEDIR}/SRCBUILD"
	fi
	if [[ ! -f "$BUILDFILE" ]]; then
		error "%s does not exist." "$BUILDFILE"
		exit $EXIT_FAILURE
	fi
	case "$BUILDFILE" in
		*/SRCBUILD) srcbuild="$(modified_srcbuild "$BUILDFILE")";;
		*)          srcbuild="$(modified_pkgbuild "$BUILDFILE")";;
	esac
	tmpfiles+=("$srcbuild")

	# Mode: checksums ######################################################

	if [[ $mode =~ checksums ]]; then
		"$makepkg" "${makepkg_opts[@]}" -g -p "$srcbuild" |
		case ${BUILDFILE##*/} in
			PKGBUILD) sed -e 's/^[a-z]/mk&/' -e 's/^\s/  &/';;
			SRCBUILD) cat;;
		esac
		exit $EXIT_SUCCESS
	fi

	# Mode: srcbuild #######################################################

	if [[ $mode =~ srcbuild ]]; then
		cat "$srcbuild"
		exit $EXIT_SUCCESS
	fi

	########################################################################

	local src="${extra_opts[0]}"
	local dst="${extra_opts[1]:-${src##*/}}"

	# Don't canonicalize $src unless mode =~ download, and we've validated
	# that $MIRRORS is configured.

	# Canonicalize $dst
	dst="$(realpath -Lm -- "$dst")"

	# Mode: download #######################################################

	if [[ $mode =~ download ]]; then
		load_conf librefetch.conf MIRRORS DOWNLOADER || exit

		# Canonicalize $src
		if [[ "$src" == libre://* ]]; then
			src="${MIRRORS[0]}/${src#libre://}"
		fi

		# check to see if $src is a candidate for create mode
		local inmirror=false;
		local mirror
		for mirror in "${MIRRORS[@]}"; do
			if [[ "$src" == "$mirror"* ]]; then
				inmirror=true
				break
			fi
		done
		if ! $inmirror; then
			# inhibit create
			mode=download
		fi

		local dlcmd="${DOWNLOADER}"
		[[ $dlcmd = *%u* ]] || dlcmd="$dlcmd %u"
		dlcmd="${dlcmd//\%o/\"\$dst\"}"
		dlcmd="${dlcmd//\%u/\"\$src\"}"

		if { eval "$dlcmd"; } >&2; then
			exit $EXIT_SUCCESS
		fi
	fi

	# Mode: create #########################################################

	if [[ $mode =~ create ]]; then
		local base_dst=${dst%.part}
		local suffix=${dst#"$base_dst"}

		if [[ $base_dst == *.sig ]]; then
			if ! [[ -e ${base_dst%.sig} ]]; then
				extra_opts=("${src%.sig}" "${base_dst%.sig}")
				doit || exit
			fi
			create_signature "${base_dst%.sig}" || exit
			if [[ -n $suffix ]]; then
				mv -f "$base_dst" "$dst"
			fi
		else
			export PKGEXT=${base_dst##*/}
			export PKGDEST=${dst%/*}
			export pkg_file=$dst

			cd "$BUILDFILEDIR"
			"$makepkg" "${makepkg_opts[@]}" -p "$srcbuild" >&2 || exit
		fi
	fi
}

# sets the variables BUILDFILE, makepkg_opts, extra_opts, mode
parse_options() {
	declare -i ret=$EXIT_SUCCESS
	local {shrt,long}{1,2}

	# makepkg options
	local makepkg_orig
	makepkg_orig="$(which makepkg)"
	shrt1=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +-(.)(,| [^<]).*/\1/p'))
	shrt2=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +-(.) <.*/\1/p'))
	long1=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn -e 's/^ +(-., )?--(\S*) [^<].*/\2/p'))
	long2=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +--(\S*) <.*/\1/p'))

	# librefetch options
	shrt1+=(C D g S M h)
	shrt2+=(p)
	long1+=(geninteg srcbuild makepkg help)
	long2+=()

	# Feed the options through getopt (sanitize them)
	local shrt long args
	shrt="$({ printf '%s\0' "${shrt1[@]}"; printf '%s:\0' "${shrt2[@]}"; } | sort -zu | xargs -0 printf '%s')"
	long="$({ printf '%s\0' "${long1[@]}"; printf '%s:\0' "${long2[@]}"; } | sort -zu | xargs -0 printf '%s,')"
	args="$(getopt -n "$cmd" -o "$shrt" -l "${long%,}" -- "$@")" || ret=$EXIT_INVALIDARGUMENT
	eval "set -- $args"
	unset shrt long args

	# Parse the options.
	local opt optarg have_optarg
	while [[ $# -gt 0 ]]; do
		opt=$1; shift
		have_optarg=false

		if { [[ $opt == --?* ]] && in_array "${opt#--}" "${long2[@]}"; } \
		|| { [[ $opt == -?   ]] && in_array "${opt#-}"  "${shrt2[@]}"; }
		then
			optarg=$1; shift
			have_optarg=true
		fi

		case "$opt" in
			-C) mode=create;;
			-D) mode=download;;
			-g|--geninteg) mode=checksums;;
			-S|--srcbuild) mode=srcbuild;;
			-M|--makepkg) mode=makepkg;;
			-p) BUILDFILE="$(realpath -Lm -- "$optarg")";;
			-h|--help) mode=help;;
			--) break;;
			*)
				makepkg_opts+=("$opt")
				if $have_optarg; then makepkg_opts+=("$optarg"); fi
				;;
		esac
	done
	extra_opts+=("$@")

	# check the number of extra_opts
	case "$mode" in
		help) # don't worry about it
			:;;
		checksums|srcbuild|makepkg) # don't take any extra arguments
			if [[ ${#extra_opts[@]} != 0 ]]; then
				print "%s: found extra non-flag arguments: %s" "$cmd" "${extra_opts[*]}" >&2
				ret=$EXIT_INVALIDARGUMENT
			fi
			;;
		*download*|*create*) # take 1 or 2 extra arguments
			if [[ ${#extra_opts[@]} != 1 ]] && [[ ${#extra_opts[@]} != 2 ]]; then
				print "%s: %d non-flag arguments found, expected 1 or 2: %s" "$cmd" ${#extra_opts[@]} >&2
				ret=$EXIT_INVALIDARGUMENT
			fi
			;;
	esac

	return $ret
}

# Modify makepkg ###############################################################

modified_makepkg() {
	local dir
	dir="$(mktemp --tmpdir --directory "${cmd}.XXXXXXXXXXX.makepkg")"
	make -s -f "$(librelib librefetchdir/Makefile)" new="$dir"
	realpath -es "$dir/makepkg"
}

# Modify PKGBUILD ##############################################################

# a string to be appended
pkgbuild_append='
# do not do split packages
if [[ ${#pkgname[@]} -gt 1 ]]; then
  if [[ -n $pkgbase ]]; then
    pkgname=("$pkgbase")
  else
    pkgname=("$pkgname")
  fi
fi

# copy source variables
source=("${mksource[@]}")       ; unset "source_${CARCH}"
noextract=("${mknoextract[@]}")

declare algo
for algo in "${known_hash_algos[@]}"; do
  eval "${algo}sums=(\"\${mk${algo}sums[@]}\")"
  unset "${algo}sums_${CARCH}"
done

depends=()                      ; unset "depends_${CARCH}"
checkdepends=()                 ; unset "checkdepends_${CARCH}"
makedepends=("${mkdepends[@]}") ; unset "makedepends_${CARCH}"

backup=()

####
# See packaging_options in the makepkg source
options=(!strip docs libtool staticlibs emptydirs !zipman !debug purge)
PURGE_TARGETS=(.bzr/ .cvs/ .git/ .hg/ .svn/ .makepkg/)

####
if ! declare -f mksource >/dev/null; then
  mksource() { :; }
fi
prepare() { :; }
build() { mksource; }
check() { :; }
package() { cp -a "$srcdir"/*/ "$pkgdir/"; }
'

modified_pkgbuild() {
	local pkgbuild=$1
	local srcbuild
	srcbuild="$(mktemp "${pkgbuild%/*}/${cmd}.XXXXXXXXXXX.PKGBUILD.to.SRCBUILD")"
	printf '%s' "$pkgbuild_append" | cat "$pkgbuild" - > "$srcbuild"
	printf '%s\n' "$srcbuild"
}


# Modify SRCBUILD ##############################################################

modified_srcbuild() {
	local orig=$1
	local new
	new="$(mktemp "${orig%/*}/${cmd}.XXXXXXXXXXX.SRCBUILD.to.SRCBUILD")"
	sed -e '/PKGDEST=/d' -e '/PKGEXT=/d' < "$orig" > "$new"
	printf '%s\n' "$new"
}

################################################################################

# This function is taken almost verbatim from makepkg
create_signature() {
	local ret=$EXIT_SUCCESS
	local filename="$1"
	msg "Signing package..."

	local SIGNWITHKEY=()
	if [[ -n $GPGKEY ]]; then
		SIGNWITHKEY=(-u "${GPGKEY}")
	fi

	gpg --detach-sign --use-agent "${SIGNWITHKEY[@]}" --no-armor "$filename" &>/dev/null || ret=$EXIT_FAILURE


	if (( ! ret )); then
		msg2 "Created signature file %s." "$filename.sig"
	else
		error "Failed to sign package file."
		return $ret
	fi
}

main "$@"
