#!/usr/bin/env bash
. "${0%/*}"/../lib/common.bashlib || exit 1 #C#
init
depend find q sort uniq xmllint
include atomf shellparse
RCD_FIELDS=dir,desc,home,remote,maint,pgo,bgo
usage <<-EOU
	Usage: ${0##*/} [option]... [atom]

	Print directory location (or change directory with *Shell Integration*
	below) plus some information corresponding to an atom either using
	/etc/portage/repos.conf or a repo list given using the -P or --path option.

	atom can be partial or missing, e.g. from dev-category/example::repo
	 > dev-category : print the category directory location
	 > example      : lookup matches in all categories
	 > example::repo: limit to a specific repo
	 > (nothing)    : print the repository's top level directory
	 > .            : run again for current directory
	In all cases, will ask if multiple results unless -1 or --first is specified.

	Options:
	  -P, --path=PATH     Colon-separated list of repos to search by priority,
	                      special keyword "default" adds all from repos.conf
	                      and "." will search current directory if it is a repo
	                      (e.g. --path="~/gentoo:default:.", see --dumpconfig
	                      or *Shell Integration* below to set permanently)
	  -D, --duplicates    Allow multiple repos with the same profiles/repo_name
	                      (e.g. keep /var/db/repos/gentoo even if have ~/gentoo)

	  -1, --first         Disable interactive prompts and always pick first choice

	  -F, --fuzzy         Always enable fuzzy search (e.g. 'gcc' matches both 'gcc'
	                      and 'gcc-config'), used by default if no exact name match
	  -f, --exact         Do not return fuzzy matches even if no exact name match
	                      (this still allows matching in multiple categories)

	  -e, --exclude=LIST  Comma-separated list of categories to exclude from
	                      searches unless explicitly specified
	                      (default: acct-user,acct-group,dev-haskell,virtual)

	      --fields=LIST   Comma-separated list of information to display,
	                      prefix with - to disable e.g. =all,-pgo
	                      (choices: ${RCD_FIELDS}
	                      all by default, pgo+bgo are only for the Gentoo repo)
	  -q, --quiet         Do not display non-error informational messages
	                      (to be further quiet, also set --no-command)

	  -R, --run=COMMAND   If atom matches a package, run COMMAND then exit with
	                      its status, see *Running Commands* for details
	                      (default: ls -1v --color=always)
	  -s, --no-capture    Redirect COMMAND's output to stderr rather than capture
	  -r, --no-command    Do not run COMMAND even if defined

	      --posix=ALIAS   Print posix sh integration, see *Shell Integration* below
	      --bash=ALIAS    Print bash integration with atom completion support
	      --fish=ALIAS    Print fish integration with atom completion support
	      --zsh=ALIAS     Print zsh integration with atom completion support
	      --compgen       Print words for completion with a partial [atom] and exit

	  -c, --no-color      Disable use of colors

	      --confdir=PATH  Configuration dir to use instead of defaults
	                      (@confdir@ + ${XDG_CONFIG_HOME:-~/.config}/@package@)
	      --dumpconfig    Display config and exit (2> ${0##*/}.conf)

	      --root=PATH     Set ROOT (command-line-only, default: '${ROOT}')
	      --eprefix=PATH  Set EPREFIX (likewise, default: '${EPREFIX}')

	  -h, --help          Display usage information and exit
	      --version       Display version information and exit

	*Known Limitations*
	Some information may be missing if ebuilds rely on eclasses to set DESCRIPTION
	or HOMEPAGE without metadata/md5-cache. cpe-type remote-ids are unsupported.
	Initial invocation may be slow until the system caches the repo directories.

	*Running Commands*
	If match a valid package, --run's COMMAND will be executed inside the directory
	allowing to e.g. show directory contents (default), eshowkw / pkgdev showkw,
	git status/log, pkgcheck, and/or display notes/reminders based on the package
	name for some ideas. The variables RCD_CATEGORY, RCD_PN, RCD_PACKAGE (cat/pn),
	and RCD_REPONAME are exported for use with external scripts.

	For example, to see keywords using app-portage/gentoolkit, try:

	    repo-cd --run="eshowkw -C" sys-apps/portage

	Note can set "run = eshowkw -C" in ${XDG_CONFIG_HOME:-~/.config}/@package@/repo-cd.conf,
	or pass --run= to the eval command in *Shell Integration* below to be permanent.

	*Shell Integration*
	To be able to change the directory of an interactive shell, a higher level
	helper is required, i.e. if ${0##*/} prints a path, then cd to it.

	For convenience, can use one of the shell integration option, e.g. --bash=rcd
	outputs code to enable \`rcd [atom]\` with atom tab completion support using
	bash. Intended to be added to ~/.bashrc and/or ~/.bash_profile (can also run
	in current shell to use immediately):

	    eval "\$(command ${0##*/} --bash=rcd)"

	Can also pass other options like --path or --run to the above for per-aliases
	effects. The above eval works the same for each supported shells, except e.g.
	swap --bash for --zsh and use ~/.zshrc, or --fish and ~/.config/fish/conf.d
EOU

setmsg 2 # stdout only used to show cd path or shell integration

optauto args "${@}" <<-EOO
	P|path=str:default:.
	D|duplicates=bool:false
	1|first=bool:false
	F|fuzzy=bool:false
	f|exact=bool:false
	e|exclude=str:acct-user,acct-group,dev-haskell,virtual
	fields=str:all
	q|quiet=bool:false
	R|run=str:ls -1v --color=always
	s|!capture=bool:true
	r|!command=bool:true
	posix=str:
	bash=str:
	fish=str:
	zsh=str:
	compgen=bool:false
	c|!color=bool:true
EOO

# rcd-shell_integration <args>
#	Print shell integration code using <args> after stripping
#	integration options.
rcd-shell_integration() {
	local -a args=()
	while (( ${#} )); do
		case ${1} in
			--bash|--fish|--posix|--zsh) shift 2; continue;;
			--bash=*|--fish=*|--posix=*|--zsh=*) shift; continue;;
		esac
		args+=("${1}")
		shift
	done

	local pargs=(
		"${RCD_SHELL}" "${0##*/}" "${args[0]+ }${args[*]@Q}"
		"${RCD_SHELL}" "${0##*/}" "${args[0]+ }${args[*]@Q}"
		"${RCD_SHELL}" "${RCD_SHELL}"
	)

	# pass words so that `repo-cd --path=...` adds new paths / rules
	# to compgen, may however silently fail if options are wrong
	if [[ ${O[bash]} ]]; then
		#!SC2016
		printf \
'%s() {
	local d
	d=$(command %q%s "${@}") && [[ -n ${d} ]] && cd "${d}"
}
_%s() {
	mapfile -t COMPREPLY < <(command %q%s "${COMP_WORDS[@]:1:COMP_CWORD-1}" --compgen -- "${2}" 2>/dev/null)
}
complete -F _%s %s\n' "${pargs[@]}"
	elif [[ ${O[fish]} ]]; then
		#!SC2016
		printf \
'function %s
	set --local d (command %q%s $argv) && [ -n "$d" ] && cd $d
end
function _%s
	set --local w (commandline -op)
	command %q%s $w[2..-2] --compgen -- $w[-1] 2>/dev/null
end
complete -e %s
complete -fa "(_%s)" %s\n' "${pargs[@]}" "${RCD_SHELL}"
	elif [[ ${O[posix]} ]]; then
		#!SC2016
		printf \
'%s() {
	_repo_cd="$(command %q%s "${@}")" && [ -n "${_repo_cd}" ] && cd "${_repo_cd}"
	unset _repo_cd
}\n' "${pargs[@]:0:3}"
	elif [[ ${O[zsh]} ]]; then
		#!SC2016
		printf \
'%s() {
	local d
	d=$(command %q%s "${@}") && [[ -n ${d} ]] && cd "${d}"
}
_%s() {
	local w
	read -cA w
	reply=(${(f)"$(command %q%s "${w[@]:1:-1}" --compgen -- "${w[-1]}" 2>/dev/null)"})
}
compctl -K _%s %s\n' "${pargs[@]}"
	fi
}

RCD_SHELL=${O[bash]:-${O[fish]:-${O[posix]:-${O[zsh]}}}}
if [[ ${RCD_SHELL} ]]; then
	[[ ${O[bash]:+x}${O[fish]:+x}${O[posix]:+x}${O[zsh]:+x} > x ]] &&
		die "cannot specify more than one shell integration option"
	rcd-shell_integration "${@}"
	exit
fi

set -- "${args[@]}"; unset args
(( ${#} <= 1 )) || die "too many atoms given, see \`${0##*/} --help\`"

[[ ${O[exclude]} =~ ^[,A-Za-z0-9+_.-]*$ ]] \
	|| die "--exclude list has invalid characters for categories"

[[ ${O[fields]}, =~ ^,*((-?(all|${RCD_FIELDS//,/|}.{0}),+)+|$)$ ]] \
	|| die "--field has invalid fields"

${O[fuzzy]} && ${O[exact]} \
	&& die "-F/--fuzzy and -f/--exact cannot be specified together"

# not perfect, but handles any sane usage
fields=
if [[ ${O[fields]} != *-all* ]]; then
	fields=${O[fields]//all/${RCD_FIELDS}}
	while [[ ${fields} =~ -([^,]+) ]]; do
		fields=${fields//-"${BASH_REMATCH[1]}"/}
		fields=${fields//"${BASH_REMATCH[1]}"/}
	done
fi
declare -A F
split fields "${fields}" ','
hasharray F fields; unset fields
(( ${#F[@]} )) || O[quiet]=true

[[ ${O[run]} ]] || O[command]=false

# rcd-ask <variable> <choice>...
#	Ask to pick from <choices> and store result in <variable>.
#	If only one choice, do nothing beside set <variable> to it.
#	If choice starts with ${HOME}, replaces with ~ only when displaying.
rcd-ask() {
	local -n outref=${1}
	shift

	if ${O[first]} || (( ${#} == 1 )); then
		outref=${1}
		return 0
	fi

	local default=" ${C[a]}(default)${C[n]}"
	local display
	local -i i
	for ((i=1; i<=${#}; i++)); do
		rcd-get_display_path display c lr "${!i}"
		rcd-msg '?:m' "${C[y]}${i}${C[a]}:${display}${default}"
		default=
	done

	rcd-msg -n '?:y' "Choice? "

	local REPLY
	read -ren ${##}
	printf -v i %u "${REPLY}" 2>/dev/null
	(( i <= 1 || i > ${#} )) && i=1 # default if non-integer / invalid reply

	outref=${!i} #!SC2034
}

# rcd-cd [atom]
#	Print directory for atom (or repo if missing) and change directory.
#	Exit with value 104 if failed to find a directory to use.
#	Return 1 if resulting directory is not a package directory.
rcd-cd() {
	local cd
	if (( ! ${#} )); then
		# if no atom, just cd to repo's top level
		rcd-ask cd "${REPOPATH[@]}"
	elif [[ ${1} == . ]]; then
		# repeat current to show information again if under a REPOPATH
		cd=
		for path in "${REPOPATH[@]}"; do
			if [[ ${PWD} =~ ^"${path}"(/.*)? ]]; then
				cd=${PWD}
				break
			fi
		done

		if [[ ! ${cd} ]]; then
			rcd-msg '!:r' "current directory is not a known repo"
			exit 104
		fi
	else
		local search=${1%/} # trim / in case did tab-complete on a directory

		# most atom elements are unused, but atomsp can sanitize input
		local atom
		atomsp atom "${search}" && [[ ${atom[3]} ]] \
			|| die "invalid search '${search}'"

		# support portage-like ::repo_name if given
		local userepo=
		[[ ${search} =~ ::([A-Za-z0-9_-]+)$ ]] && userepo=${BASH_REMATCH[1]}

		local cat=${atom[2]:-}
		local name=${atom[3]} # can be a category-only search

		# search paths separately to sort each individually in REPOPATH order
		local path
		local -a choice=()
		${O[exact]} && search=${name} || search="*${name}*"
		search=${search//[_-]/[_-]} # "case" insensitive _ and -
		for path in "${REPOPATH[@]}"; do
			[[ ! ${userepo} || ${REPONAME[${path}/]} == "${userepo}" ]] || continue

			if [[ ${cat} && -d ${path}/${cat}/${name} ]] && ! ${O[fuzzy]}; then
				# exact match and no fuzzy, print as-is ignoring exceptions
				printf '%s\0' "${path}/${cat}/${name}"
			else
				rcd-find 1 2 "${path}" -iname "${search}" -print0 | sort -z || die
			fi
		done | mapfile -td '' choice

		if (( ! ${#choice[@]} )); then
			rcd-msg '!:r' "no non-excluded match found for '${C[m]}${name}${C[n]}'${userepo:+ in ::${userepo}}"
			exit 104
		fi

		if ! ${O[fuzzy]} && ! ${O[exact]}; then
			# only do fuzzy if no exact name matches when fuzziness is
			# undefined, and post-process to avoid running find(1) twice
			local base
			local -a exact=()
			for path in "${choice[@]}"; do
				base=${path##*/}
				base=${base//[_-]/[_-]}
				[[ ${name,,} == ${base,,} ]] && exact+=("${path}") #!SC2053
			done
			(( ${#exact[@]} )) && choice=("${exact[@]}")
		fi

		rcd-ask cd "${choice[@]}"
	fi

	if ! ${O[quiet]} && [[ -v F[dir] ]]; then
		rcd-get_display_path path c lr "${cd}"
		rcd-msg '>:y:c' "${path}"
	fi
	cd "${cd}" || die "failed to cd '${cd}'"
	echo "${cd}" # for the interactive shell to use

	# roughly figure out if find(1)+rcd-ask gave a package directory without
	# assuming from files, e.g. skel.ebuild is not from a package
	path=${PWD%/*/*}
	[[ ${REPONAME[${path%/}/]+x} ]]
}

# rcd-find <mindepth> <maxdepth> <path>... <extra-find-args>
#	Run find(1) with preset exclusion/depth/directory rules on paths.
#	<extra-find-args> should contain the action and optionally extra rules,
#	e.g. -ipath '*/category/package' -print0
rcd-find() {
	local depth=(-mindepth "${1}" -maxdepth "${2}")
	shift 2

	local -a paths=()
	while (( ${#} )); do
		[[ ${1::1} == - ]] && break
		paths+=("${1}")
		shift
	done

	local omitdirs
	split omitdirs "${O[exclude]}" ','

	omitdirs+=(
		# omit a few known non-category directories (ideally would
		# load category files, but REPOPATH may lack the full picture)
		distfiles
		eclass
		licenses
		metadata
		packages
		profiles
		scripts
	)

	local omit=( -name '.*' ) # .git and other hidden files

	local dir path
	for path in "${paths[@]}"; do
		# add path to ensure doesn't match on packages themselves
		# (loop over given can't safely rely on word splitting to /#/-o -path})
		for dir in "${omitdirs[@]}"; do
			omit+=(-o -path "${path}/${dir}")
		done
	done

	# not trying to use pquery/portage/bashglobs given find(1) is faster
	# and makes this simpler for ordering and custom --path repos
	find -H "${paths[@]}" "${depth[@]}" \
		-type d \( "${omit[@]}" \) -prune -o -type d "${@}" || die
}

# rcd-get_display_path <variable> <base-color> <cat/pkg-color> <path>
#	Set <variable> with <path> adjusted for display with mixed colors
#	for cat/pkg to stand out and ${HOME} be replaced by ~.
rcd-get_display_path() {
	local -n outref=${1}

	local path_cat path_pn path_base
	path_cat=${4%/*}
	path_cat=${path_cat##*/}
	path_pn=${4##*/}
	path_base=${4%"${path_cat}/${path_pn}"}

	if [[ ! ${REPONAME[${path_base%/}/]+x} ]]; then
		path_cat=${path_pn}
		path_pn=
		path_base=${4%"${path_cat}"}
		if [[ ! ${REPONAME[${path_base%/}/]+x} ]]; then
			path_cat=
			path_base=${4}
		fi
	fi

	outref=${C[${2}]}${path_base/#"${HOME}"/\~}${path_cat:+${C[${3}]}${path_cat}${path_pn:+/${path_pn}}}${C[n]}
}

# rcd-get_expand_tilde <variable> <string>
#	Expand ~ in <string> then store back in <variable>
rcd-get_expand_tilde() {
	local -n outref=${1}
	if [[ ${2::1} == '~' ]]; then
		# use printf %q to escape anything potentially harmful then unescape ~
		# and let the shell handle all use-cases, e.g. ~user/ ~+/ etc...
		# Not that there's a real need for safety here, but it doesn't hurt.
		printf -v outref %q "${2}"
		eval "outref=${outref/#\\\~/\~}"
	else
		outref=${2}
	fi
}

# rcd-get_metadata <array> <path[]> [attribute]...
#	Get metadata.xml (current dir) results using xml <path> then store in
#	<array>. If [attribute], will be set at start of array with a space,
#	[] indicates the single element to iterate and use [attribute] for.
#	e.g.
#	'upstream/remote-id[]' type -> "github repo/location"
#	'maintainer[]/email' type missing -> "person  larry@gentoo.org"
#	Return 1 on xmllint errors (malformed, or couldn't read metadata.xml)
rcd-get_metadata() {
	local -n outref=${1}
	local path=${2}
	shift 2

	local -i i max
	max=$(xmllint --nonet --xpath \
		"count(//pkgmetadata/${path%%'[]'*})" metadata.xml) || return 1

	local xpath=
	if (( ${#} )); then
		while (( ${#} )); do
			xpath+="//pkgmetadata/${path/'[]'*/[]/@${1}},' ',"
			shift
		done
		xpath="concat(${xpath}normalize-space(//pkgmetadata/${path}))"
	else
		xpath="normalize-space(//pkgmetadata/${path})"
	fi

	outref=()
	for ((i=1; i<=max; i++)); do
		outref+=("$(xmllint --nonet --xpath \
			"${xpath//'[]'/[${i}]}" metadata.xml)") || return 1
	done
}

# rcd-get_metadata_remote_urls <array>
#	Set <array> with remote urls defined by metadata.xml remote-ids.
#	Issues a warning if unknown remote-ids.
#	Return 1 if rcd-get_metadata failed
rcd-get_metadata_remote_urls() {
	local -n outref=${1} #!SC2178
	outref=()

	local -a remoteids
	rcd-get_metadata remoteids 'upstream/remote-id[]' type || return 1

	local entry remoteids
	outref=()
	for entry in "${remoteids[@]}"; do
		id=${entry#* }

		# see https://wiki.gentoo.org/wiki/Project:Quality_Assurance/Upstream_remote-id_types
		case ${entry%% *} in
			bitbucket) outref+=("https://bitbucket.org/${id}/");;
			codeberg) outref+=("https://codeberg.org/${id}");;
			cpan-module) outref+=("https://metacpan.org/pod/${id}");;
			cpan) outref+=("https://metacpan.org/dist/${id}");;
			cpe) continue;; # unhandled
			cran) outref+=("https://cran.r-project.org/web/packages/${id}/");;
			ctan) outref+=("https://ctan.org/pkg/${id}/");;
			freedesktop-gitlab) outref+=("https://gitlab.freedesktop.org/${id}/");;
			gentoo) outref+=("https://gitweb.gentoo.org/${id}.git/");;
			github) outref+=("https://github.com/${id}/");;
			gitlab) outref+=("https://gitlab.com/${id}/");;
			gnome-gitlab) outref+=("https://gitlab.gnome.org/${id}/");;
			google-code) outref+=("https://code.google.com/archive/p/${id}/");;
			hackage) outref+=("https://hackage.haskell.org/package/${id}");;
			heptapod) outref+=("https://foss.heptapod.net/${id}/");;
			kde-invent) outref+=("https://invent.kde.org/${id}/");;
			launchpad) outref+=("https://launchpad.net/${id}/");;
			nimble) outref+=("https://nimble.directory/pkg/${id}/");;
			osdn) outref+=("https://osdn.net/projects/${id}/");;
			pear) outref+=("https://pear.php.net/package/${id}/");;
			pecl) outref+=("https://pecl.php.net/package/${id}/");;
			pypi) outref+=("https://pypi.org/project/${id}/");;
			rubygems) outref+=("https://rubygems.org/gems/${id}/");;
			savannah) outref+=("https://savannah.gnu.org/projects/${id}/");;
			savannah-nongnu) outref+=("https://savannah.nongnu.org/projects/${id}/");;
			sourceforge) outref+=("https://sourceforge.net/projects/${id}/");;
			sourcehut) outref+=("https://sr.ht/${id}/");;
			vim) outref+=("https://www.vim.org/scripts/script.php?script_id=${id}");;
			*)
				rcd-msg '!:b:a' "metadata.xml: unknown remote-id ${entry%% *}"
				continue
			;;
		esac
	done
}

# rcd-msg [-n] <char>[:<prefix-color-id>:[message-color-id]] <message>...
#	Print message with <char> prefix, e.g. msg '!:r' "prefixed by red !".
#	Multiple arguments will result in multiple lines with same prefix.
#	Skip trailing newline if -n.
#	More complex coloring can be used in the message itself.
rcd-msg() {
	local trail=1
	if [[ ${1} == -n ]]; then
		trail=
		shift
	fi

	local -a m
	split m "${1}" ':'
	shift

	printf " ${m[1]+${C[${m[1]}]}}${m[0]}${m[1]+${C[n]}} ${m[2]+${C[${m[2]}]}}%s${m[2]+${C[n]}}${trail:+\n}" \
		"${@}" >&2
}

# rcd-print_compgen [current]
#	Print possible words for tab completion based on REPOPATH.
rcd-print_compgen() {
	# do nothing if no arguments rather print the rather slow full list
	(( ${#} )) || return 0

	local -a gen
	local search=${1//[_-]/[_-]} # "case" insensitive _ and -
	{
		if [[ ${1} == */* ]]; then
			rcd-find 2 2 "${REPOPATH[@]}" -ipath "*/${search}*" -printf '%P\n'
		else
			# process categories separately to add /
			rcd-find 1 1 "${REPOPATH[@]}" -iname "${search}*" -printf '%f/\n'
			rcd-find 2 2 "${REPOPATH[@]}" -iname "${search}*" -printf '%f\n'
		fi
	} | sort | uniq | map gen

	# if _ or - had an exact match, discard the mismatching ones as the shell
	# will not know how to handle this
	local exactmatch=false
	if [[ ${1} == *[_-]* ]]; then
		local match
		for match in "${gen[@]}"; do
			if [[ ${match} == "${1}"* ]]; then
				exactmatch=true
				echo "${match}"
			fi
		done
	fi
	${exactmatch} || printarray gen
}

# rcd-print_command
#	Run ${O[run]}, output through rcd-msg, and return its exit status
rcd-print_command() {
	local -x RCD_CATEGORY=${PWD%/*}
	RCD_CATEGORY=${RCD_CATEGORY##*/}
	local -x RCD_PN=${PWD##*/}
	local -x RCD_PACKAGE=${RCD_CATEGORY}/${RCD_PN}
	local -x RCD_REPONAME=${REPONAME[${PWD%/*/*}/]}

	# roughly allow tilde expansion for any arguments
	local arg
	local -a cmd=()
	for arg in ${O[run]}; do
		rcd-get_expand_tilde arg "${arg}"
		cmd+=("${arg}")
	done

	if ${O[capture]}; then
		command -- "${cmd[@]}" |& map output
		(( ret=PIPESTATUS[0] )) && mod='-:r' || mod='+:g'
		(( ${#output[@]} )) && rcd-msg "${mod}" "${output[@]}"
		return ${ret}
	fi

	"${cmd[@]}" >&2
}

# rcd-print_package_data
#	Print basic info package in ${PWD}, and aggregates HOMEPAGEs and
#	remote-ids to print all while trying to avoid duplicates.
rcd-print_package_data() {
	local -a homesort=() maintainers=()
	local -A homeindex
	local cache description= ebuild data note pvr

	if [[ -v F[home] || -v F[desc] ]]; then
		local -i i
		for ((i=0; i<${#ATOM[@]}; i++)); do
			local -n atom=${ATOM[i]}
			local -n si=${SI[i]}
			pvr=${atom[4]}${atom[5]:+-r${atom[5]}}

			# check md5-cache to see eclass-set values (inherit is unhandled),
			# and then ebuild to be up to date or fallback if cache is missing
			cache=../../metadata/md5-cache/${atom[2]}/${atom[3]}-${pvr}
			if [[ -d ../../metadata/md5-cache && -f ${cache} ]]; then
				note="${pvr}, cached"
				while IFS= read -r data; do
					case ${data} in
						DESCRIPTION=*)
							description="${C[ly]}${data#DESCRIPTION=} ${C[a]}(${note})${C[n]}"
						;;
						HOMEPAGE=*)
							[[ -v F[home] ]] &&
								for data in ${data#HOMEPAGE=}; do
									_rcd_print_package_data_add_homepage
								done
						;;
					esac
				done < "${cache}" || die "failed to read '${cache}'"
			fi

			note=${pvr}

			[[ ${si[DESCRIPTION]:-} ]] &&
				description="${C[ly]}${si[DESCRIPTION]} ${C[a]}(${note})${C[n]}"
			[[ -v F[home] ]] &&
				for data in ${si[HOMEPAGE]:-}; do
					_rcd_print_package_data_add_homepage
				done
		done
	fi

	if [[ ( -v F[remote] || -v F[maint] ) && -f metadata.xml ]]; then
		local -a metabugs metachangelog metadocs metamaints metaurls
		# stop at once if any errors as will likely all fail and be noisy
		if { [[ ! -v F[remote] ]] || {
				rcd-get_metadata metabugs 'upstream/bugs-to[]' &&
				rcd-get_metadata metachangelog 'upstream/changelog[]' &&
				rcd-get_metadata metadocs 'upstream/doc[]' &&
				rcd-get_metadata_remote_urls metaurls
			} } &&
			{ [[ ! -v F[maint] ]] ||
				rcd-get_metadata metamaints 'maintainer[]/email' type proxied
			}
		then
			note=
			for data in "${metaurls[@]}"; do
				_rcd_print_package_data_add_homepage
			done

			note=doc
			for data in "${metadocs[@]}"; do
				_rcd_print_package_data_add_homepage
			done

			note=bugs-to
			for data in "${metabugs[@]}"; do
				_rcd_print_package_data_add_homepage
			done

			note=changelog
			for data in "${metachangelog[@]}"; do
				_rcd_print_package_data_add_homepage
			done

			for data in "${metamaints[@]}"; do
				if [[ ${data} =~ ([^ ]*)\ ([^ ]*)\ (.+) ]]; then
					if [[ ${BASH_REMATCH[2]} == proxy ]]; then
						data=${C[a]}
					elif [[ ${BASH_REMATCH[2]} == yes ]]; then
						data=${C[m]}
					elif  [[ ${BASH_REMATCH[1]} == project ]]; then
						data=${C[g]}
					else
						data=${C[lm]}
					fi
					maintainers+=("${data}${BASH_REMATCH[3]}${C[n]}")
				fi
			done
		else
			rcd-msg '!:b:a' "metadata.xml: issues reading, info may be missing"
		fi
	fi
	(( ${#maintainers[@]} )) || maintainers=("${C[a]}maintainer-needed${C[n]}")

	# display
	[[ -v F[desc] && ${description} ]] && rcd-msg D:la "${description}"

	[[ -v F[home] || -v F[remote] ]] &&
		for data in "${homesort[@]}"; do
			rcd-msg H:a "${homeindex[${data}]}"
		done

	# Also show pgo and bgo links if ::gentoo, non-metadata simple links
	# could use a --run=script to display but these are rather common
	data=${PWD%/*/*}
	if [[ ${REPONAME[${data%/}/]} == gentoo ]]; then
		data=${PWD#"${data}"/}
		[[ -v F[pgo] ]] &&
			rcd-msg G:m:lb "https://packages.gentoo.org/packages/${data}"
		[[ -v F[bgo] ]] &&
			rcd-msg G:m:lb "https://bugs.gentoo.org/buglist.cgi?quicksearch=${data/\//%2F}"
	fi

	[[ -v F[maint] ]] && rcd-msg M:c "${maintainers[*]}"
}
_rcd_print_package_data_add_homepage() {
	# build simplified HOMEPAGE index (no http:, trailing /, nor index.*) to
	# detect most duplicates, latest name will be used unless it's a remote-id

	local trim=${data#*://}
	trim=${trim%index.*}
	trim=${trim%/}

	# remember sort order given bash doesn't with hashes
	[[ ${homeindex[${trim}]+x} ]] || homesort+=("${trim}")

	homeindex[${trim}]="${C[lb]}${data}${note:+ ${C[a]}(${note})}${C[n]}"
}

# rcd-set_atom_and_si [nosi]
#	Fill ATOM and SI arrays for all valid ebuilds in current directory
#	(if any). Each element has a atomsp() and shellimport() reference.
#	Skip the more expensive SI if [nosi].
declare -a ATOM=() SI=()
rcd-set_atom_and_si() {
	local cat ebuild
	local -i i=0
	local nosi=false
	[[ ${1:-} == nosi ]] && nosi=true

	cat=${PWD%/*}
	cat=${cat##*/}

	set +f
	local -a ebuilds=( *.ebuild )
	set -f

	# true PMS-sorting is not essential but still at least sort -V so
	# e.g. HOMEPAGE outputs have right order
	# TODO?: atomf.bashlib could have basic compare/sort, see eapi7-ver.eclass
	printarray ebuilds | sort -V | map ebuilds || die

	for ebuild in "${ebuilds[@]}"; do
		local -n atom=RCD_ATOM_${i}
		local -n si=RCD_SI_${i}
		if atomsp "${!atom}" "=${cat}/${ebuild}" &&
			[[ ${atom[2]:-} && ${atom[3]:-} && ${atom[4]:-} ]]
		then
			if ${nosi}; then
				ATOM+=("${!atom}")
				i+=1
			elif shellimport "${!si}" "${ebuild}"; then
				ATOM+=("${!atom}")
				SI+=("${!si}")
				i+=1
			else
				rcd-msg '!:b:a' "=${cat}/${ebuild%.ebuild}: ignored (shellimport failed)"
			fi
		else
			rcd-msg '!:b:a' "=${cat}/${ebuild%.ebuild}: ignored (invalid atom)"
		fi
	done
}

# rcd-set_repopath
#	Set REPOPATH array based on O[path] string. Also sets REPONANE[${path%/}/]
#	for name lookup or testing if ${path} is a repo.
#	Skip repos with the same name in metadata/layout.conf so that e.g.
#	path=~/gentoo:default doesn't asks to pick every time.
#	Skip and warn for invalid paths, dies if none left.
declare -A REPONAME
declare -a REPOPATH=()
rcd-set_repopath() {
	local -A nameindex
	local -a paths
	split paths "${O[path]}" ':'

	local path
	for path in "${paths[@]}"; do
		[[ ${path} ]] || continue

		if [[ ${path} == default ]]; then
			# use q to avoid portageq's slow startup
			unset DEBUG # avoid q noise if set
			q -Cqo | while IFS= read -r path; do
				# gentoo: ${ROOT}/var/db/repos/gentoo (main)
				path=${path% (*)}
				path=${path#*: }

				# q properly uses repos.conf from prefix but does not include
				# the prefix in the output (just ROOT), so do hacky stuff
				[[ ${EPREFIX} && ${path} != "${EROOT}/"* ]] &&
					path=${EROOT}${path#"${ROOT}"}

				_rcd-set_repopath_add_if_unique
				:
			done || die "failed to read repos.conf with q -Cqo"
		elif [[ ${path} == . ]]; then
			# check 2 leading directories for packages to packages switching
			path=${PWD}
			if [[ ! -e ${path}/profiles/repo_name ]]; then
				path=${path%/*}
				[[ ! -e ${path}/profiles/repo_name ]] && path=${path%/*}
			fi
			[[ -e ${path}/profiles/repo_name ]] &&
				_rcd-set_repopath_add_if_unique
		else
			# allow tilde expansion for convenience
			rcd-get_expand_tilde path "${path}"
			_rcd-set_repopath_add_if_unique
		fi
	done

	(( ${#REPOPATH[@]} )) || die "no valid repo paths found, see \`${0##*/} --help\`"
}
_rcd-set_repopath_add_if_unique() {
	if absdir path; then
		local file=${path}/profiles/repo_name
		if [[ -f ${file} ]]; then
			name=$(<"${file}") || die "failed to read '${file}'"
			if [[ ! ${nameindex[${name}]+x} ]] || ${O[duplicates]}; then
				REPONAME[${path%/}/]=${name}
				REPOPATH+=("${path}")
				nameindex[${name}]=
			fi
		else
			rcd-msg '!:b:a' "${path}: ignored path (lacks profiles/repo_name)"
		fi
	else
		rcd-msg '!:b:a' "${path}: ignored path (not a directory)"
	fi
}

rcd-set_repopath
if ${O[compgen]}; then
	rcd-print_compgen "${@}"
elif rcd-cd "${@}"; then
	# avoid doing some slower operations if not needed
	if ${O[command]} || ! ${O[quiet]}; then
		# get ATOM either way as a sanity check
		${O[quiet]} && rcd-set_atom_and_si nosi || rcd-set_atom_and_si

		if (( ${#ATOM[@]} )); then
			${O[quiet]} || rcd-print_package_data

			if ${O[command]}; then
				rcd-print_command
				exit ${?}
			fi
		fi
	fi
fi

:

# vim: ts=4
