From 4673ad6c89bbdca632b22edfc2ef35486b7a635b Mon Sep 17 00:00:00 2001
From: Jelle van der Waa <jelle@archlinux.org>
Date: Sat, 1 Jul 2023 15:21:32 +0200
Subject: feat(search): add subcommand to search across the packaging group

Search for an expression across the GitLab packaging group. To use a
filter, include it in your query. You may use wildcards (*) to use glob
matching. Available filters for the blobs scope: path, extension.

Every usage of the search command must be authenticated. Consult the
'pkgctl auth' command to authenticate with GitLab or view the
authentication status.

This command uses bats for pretty printing the results including line
numbers and syntax highlighting.

Component: pkgctl search
Co-authored-by: Christian Heusel <christian@heusel.eu>
Co-authored-by: Levente Polyak <anthraxx@archlinux.org>
---
 doc/man/pkgctl-search.1.asciidoc | 58 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)
 create mode 100644 doc/man/pkgctl-search.1.asciidoc

(limited to 'doc/man/pkgctl-search.1.asciidoc')

diff --git a/doc/man/pkgctl-search.1.asciidoc b/doc/man/pkgctl-search.1.asciidoc
new file mode 100644
index 0000000..fb79b88
--- /dev/null
+++ b/doc/man/pkgctl-search.1.asciidoc
@@ -0,0 +1,58 @@
+pkgctl-search(1)
+================
+
+Name
+----
+pkgctl-search - Search for an expression across the GitLab packaging group
+
+Synopsis
+--------
+pkgctl search [OPTIONS] QUERY
+
+Description
+-----------
+
+Search for an expression across the GitLab packaging group.
+
+To use a filter, include it in your query. You may use wildcards (*) to
+use glob matching.
+
+Available filters for the blobs scope: path, extension
+
+Every usage of the search command must be authenticated. Consult the
+'pkgctl auth' command to authenticate with GitLab or view the authentication
+status.
+
+Search Tips
+-----------
+
+	Syntax  Description    Example
+	───────────────────────────────────────
+	"       Exact search   "gem sidekiq"
+	~       Fuzzy search   J~ Doe
+	|       Or             display | banner
+	+       And            display +banner
+	-       Exclude        display -banner
+	*       Partial        bug error 50*
+	\       Escape         \*md
+	#       Issue ID       #23456
+	!       Merge request  !23456
+
+Options
+-------
+
+*--json*::
+	Enable printing results in JSON 
+
+*--no-default-filter*::
+	Do not apply default filter (like -path:keys/pgp/*.asc)
+
+*-h, --help*::
+	Show a help text
+
+See Also
+--------
+
+linkman:pkgctl-auth[1]
+
+include::include/footer.asciidoc[]
-- 
cgit v1.2.3-54-g00ecf


From d0dc0e1a32d6ed9b166eca777f7fb7071c4c2df1 Mon Sep 17 00:00:00 2001
From: Levente Polyak <anthraxx@archlinux.org>
Date: Fri, 5 Jan 2024 19:23:52 +0100
Subject: feat(search): add optional plain output formatting

This allows to run the search command without bats, which is not used in
the default pretty output format.

Component: pkgctl search
Signed-off-by: Levente Polyak <anthraxx@archlinux.org>
---
 README.md                           |  5 +-
 contrib/completion/bash/devtools.in | 11 ++++-
 contrib/completion/zsh/_devtools.in |  6 ++-
 doc/man/pkgctl-search.1.asciidoc    | 23 +++++++--
 src/lib/common.sh                   |  4 +-
 src/lib/search.sh                   | 93 +++++++++++++++++++++++++++++++++----
 src/lib/valid-search.sh             | 11 +++++
 7 files changed, 134 insertions(+), 19 deletions(-)
 create mode 100644 src/lib/valid-search.sh

(limited to 'doc/man/pkgctl-search.1.asciidoc')

diff --git a/README.md b/README.md
index 6c36a37..ca761db 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,6 @@ Component: pkgctl db remove
 - arch-install-scripts
 - awk
 - bash
-- bats
 - binutils
 - coreutils
 - diffutils
@@ -87,6 +86,10 @@ Component: pkgctl db remove
 - mercurial
 - subversion
 
+### Optional Dependencies
+
+- bats (pretty printing)
+
 ### Development Dependencies
 
 - asciidoc
diff --git a/contrib/completion/bash/devtools.in b/contrib/completion/bash/devtools.in
index 155bb7e..4c7b73a 100644
--- a/contrib/completion/bash/devtools.in
+++ b/contrib/completion/bash/devtools.in
@@ -9,6 +9,8 @@ source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-tags.sh
 source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-repos.sh
 # shellcheck source=src/lib/valid-inspect.sh
 source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-inspect.sh
+# shellcheck source=src/lib/valid-search.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-search.sh
 
 _binary_arch=${DEVTOOLS_VALID_ARCHES[*]:0:-1}
 _colors=(never always auto)
@@ -333,11 +335,15 @@ _pkgctl_repo_web_opts() { _filedir -d; }
 
 
 _pkgctl_search_args=(
-	--json
 	--no-default-filter
+	--json
+	-F --format
+	-N --no-line-number
 	-h --help
 )
 _pkgctl_search_opts() { :; }
+_pkgctl_search_args__format_opts() { _devtools_completions_search_format; }
+_pkgctl_search_args_F_opts() { _devtools_completions_search_format; }
 
 
 _pkgctl_diff_args=(
@@ -391,6 +397,9 @@ _devtools_completions_protocol() {
 _devtools_completions_inspect() {
 	mapfile -t COMPREPLY < <(compgen -W "${DEVTOOLS_VALID_INSPECT_MODES[*]}" -- "$cur")
 }
+_devtools_completions_search_format() {
+	mapfile -t COMPREPLY < <(compgen -W "${valid_search_output_format[*]}" -- "$cur")
+}
 
 __devtools_complete() {
 	local service=$1
diff --git a/contrib/completion/zsh/_devtools.in b/contrib/completion/zsh/_devtools.in
index 120b47a..448fbed 100644
--- a/contrib/completion/zsh/_devtools.in
+++ b/contrib/completion/zsh/_devtools.in
@@ -9,6 +9,8 @@ source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-tags.sh
 source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-repos.sh
 # shellcheck source=src/lib/valid-inspect.sh
 source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-inspect.sh
+# shellcheck source=src/lib/valid-search.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-search.sh
 
 _binary_arch=${DEVTOOLS_VALID_ARCHES[*]:0:-1}
 _colors=(never always auto)
@@ -140,8 +142,10 @@ _pkgctl_repo_web_args=(
 )
 
 _pkgctl_search_args=(
-	'--json[Enable printing results in JSON]'
 	'--no-default-filter[Do not apply default filter (like -path:keys/pgp/*.asc)]'
+	'--json[Enable printing results in JSON]'
+	'(-F --format)'{-F,--format}"[Controls the formatting of the results]:format:($valid_search_output_format[*])"
+	'(-N --no-line-number)'{-N,--no-line-number}"[Don't show line numbers when formatting results]"
 	'(-h --help)'{-h,--help}'[Display usage]'
 	'1:query'
 )
diff --git a/doc/man/pkgctl-search.1.asciidoc b/doc/man/pkgctl-search.1.asciidoc
index fb79b88..8172b00 100644
--- a/doc/man/pkgctl-search.1.asciidoc
+++ b/doc/man/pkgctl-search.1.asciidoc
@@ -20,7 +20,7 @@ use glob matching.
 Available filters for the blobs scope: path, extension
 
 Every usage of the search command must be authenticated. Consult the
-'pkgctl auth' command to authenticate with GitLab or view the authentication
+`'pkgctl auth'` command to authenticate with GitLab or view the authentication
 status.
 
 Search Tips
@@ -41,14 +41,27 @@ Search Tips
 Options
 -------
 
-*--json*::
-	Enable printing results in JSON 
+*-h, --help*::
+	Show a help text
+
+Filter Options
+--------------
 
 *--no-default-filter*::
 	Do not apply default filter (like -path:keys/pgp/*.asc)
 
-*-h, --help*::
-	Show a help text
+Output Options
+--------------
+
+*--json*::
+	Enable printing in JSON; Shorthand for `'--format json'`
+
+*-F, --format* 'FORMAT'::
+	Controls the formatting of the results; `FORMAT` is `'pretty'`, `'plain'`,
+	or `'json'` (default `pretty`)
+
+*-N, --no-line-number*::
+	Don't show line numbers when formatting results
 
 See Also
 --------
diff --git a/src/lib/common.sh b/src/lib/common.sh
index 29b0343..17b91bc 100644
--- a/src/lib/common.sh
+++ b/src/lib/common.sh
@@ -33,9 +33,11 @@ export PKGBASE_MAINTAINER_URL=https://archlinux.org/packages/pkgbase-maintainer
 # check if messages are to be printed using color
 if [[ -t 2 && "$TERM" != dumb ]] || [[ ${DEVTOOLS_COLOR} == always ]]; then
 	colorize
+	PURPLE="$(tput setaf 5)"
+	DARK_GREEN="$(tput setaf 2)"
 else
 	# shellcheck disable=2034
-	declare -gr ALL_OFF='' BOLD='' BLUE='' GREEN='' RED='' YELLOW=''
+	declare -gr ALL_OFF='' BOLD='' BLUE='' GREEN='' RED='' YELLOW='' PURPLE=''
 fi
 
 stat_busy() {
diff --git a/src/lib/search.sh b/src/lib/search.sh
index cf64db3..862af25 100644
--- a/src/lib/search.sh
+++ b/src/lib/search.sh
@@ -12,7 +12,10 @@ source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh
 source "${_DEVTOOLS_LIBRARY_DIR}"/lib/cache.sh
 # shellcheck source=src/lib/api/gitlab.sh
 source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh
+# shellcheck source=src/lib/valid-search.sh
+source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-search.sh
 
+source /usr/share/makepkg/util/util.sh
 source /usr/share/makepkg/util/message.sh
 
 set -eo pipefail
@@ -48,16 +51,33 @@ pkgctl_search_usage() {
 		    !       Merge request  !23456
 
 		OPTIONS
-		    --json               Enable printing results in JSON
-		    --no-default-filter  Do not apply default filter (like -path:keys/pgp/*.asc)
-		    -h, --help           Show this help text
+		    -h, --help            Show this help text
+
+		FILTER OPTIONS
+		    --no-default-filter   Do not apply default filter (like -path:keys/pgp/*.asc)
+
+		OUTPUT OPTIONS
+		    --json                Enable printing in JSON; Shorthand for '--format json'
+		    -F, --format FORMAT   Controls the formatting of the results; FORMAT is 'pretty',
+		                          'plain', or 'json' (default: pretty)
+		    -N, --no-line-number  Don't show line numbers when formatting results
 
 		EXAMPLES
 		    $ ${COMMAND} linux
-		    $ ${COMMAND} '"pytest -v" +PYTHONPATH'
+		    $ ${COMMAND} --json '"pytest -v" +PYTHONPATH'
 _EOF_
 }
 
+pkgctl_search_check_option_group_format() {
+	local option=$1
+	local output_format=$2
+	if [[ -n ${output_format} ]]; then
+		die "The argument '%s' cannot be used with one or more of the other specified arguments" "${option}"
+		exit 1
+	fi
+	return 0
+}
+
 pkgctl_search() {
 	if (( $# < 1 )); then
 		pkgctl_search_usage
@@ -66,15 +86,17 @@ pkgctl_search() {
 
 	# options
 	local search
-	local formatter=pretty
+	local output_format=
 	local use_default_filter=1
+	local line_numbers=1
 
 	# variables
+	local bats_style="header,grid"
 	local default_filter="-path:keys/pgp/*.asc"
 	local graphql_lookup_batch=200
 	local output result query entries from until length
 	local project_name_cache_file project_name_lookup project_ids project_id project_name project_slice
-	local mapping_output path startline data
+	local mapping_output path startline currentline data line
 
 	while (( $# )); do
 		case $1 in
@@ -82,12 +104,26 @@ pkgctl_search() {
 				pkgctl_search_usage
 				exit 0
 				;;
+			--no-default-filter)
+				use_default_filter=0
+				shift
+				;;
 			--json)
-				formatter=json
+				pkgctl_search_check_option_group_format "$1" "${output_format}"
+				output_format=json
 				shift
 				;;
-			--no-default-filter)
-				use_default_filter=0
+			-F|--format)
+				(( $# <= 1 )) && die "missing argument for %s" "$1"
+				pkgctl_search_check_option_group_format "$1" "${output_format}"
+				output_format="${2}"
+				if ! in_array "${output_format}" "${valid_search_output_format[@]}"; then
+					die "Unknown output format: %s" "${output_format}"
+				fi
+				shift 2
+				;;
+			-N|--no-line-number)
+				line_numbers=0
 				shift
 				;;
 			--)
@@ -114,16 +150,35 @@ pkgctl_search() {
 		search+=" ${default_filter}"
 	fi
 
+	# assign default output format
+	if [[ -z ${output_format} ]]; then
+		output_format=pretty
+	fi
+
+	# check for optional dependencies
+	if [[ ${output_format} == pretty ]] && ! command -v bats &>/dev/null; then
+		warning "Failed to find optional dependency 'bats': falling back to plain output"
+		output_format=plain
+	fi
+
+	# populate line numbers option
+	if (( line_numbers )); then
+		bats_style="numbers,${bats_style}"
+	fi
+
+	# call the gitlab search API
 	stat_busy "Querying gitlab search api"
 	output=$(gitlab_api_search "${search}")
 	stat_done
 
+	# collect project ids whose name needs to be looked up
 	project_name_cache_file=$(get_cache_file gitlab/project_id_to_name)
 	lock 11 "${project_name_cache_file}" "Locking project name cache"
 	mapfile -t project_ids < <(
 		jq --raw-output '[.[].project_id] | unique[]' <<< "${output}" | \
 			grep --invert-match --file <(awk '{ print $1 }' < "${project_name_cache_file}" ))
 
+	# look up project names
 	stat_busy "Querying project names"
 	local entries="${#project_ids[@]}"
 	local until=0
@@ -171,7 +226,7 @@ pkgctl_search() {
 	lock_close 11
 
 	# output mode JSON
-	if [[ ${formatter} == json ]]; then
+	if [[ ${output_format} == json ]]; then
 		jq --from-file <(
 			for project_id in $(jq '.[].project_id' <<< "${output}"); do
 				project_name=${project_name_lookup[${project_id}]}
@@ -197,6 +252,23 @@ pkgctl_search() {
 			unset "data[${#data[@]}-1]"
 		fi
 
+		# output mode plain
+		if [[ ${output_format} == plain ]]; then
+			printf "%s%s%s\n" "${PURPLE}" "${project_name}/${path}" "${ALL_OFF}"
+
+			currentline=${startline}
+			for line in "${data[@]}"; do
+				if (( line_numbers )); then
+					line="${DARK_GREEN}${currentline}${ALL_OFF}: ${line}"
+					currentline=$(( currentline + 1 ))
+				fi
+				printf "%s\n" "${line}"
+			done
+			printf "\n"
+
+			continue
+		fi
+
 		# prepend empty lines to match startline
 		if (( startline > 1 )); then
 			mapfile -t data < <(
@@ -210,6 +282,7 @@ pkgctl_search() {
 			--line-range "${startline}:" \
 			--paging=never \
 			--force-colorization \
+			--style "${bats_style}" \
 			--map-syntax "PKGBUILD:Bourne Again Shell (bash)" \
 			--map-syntax ".SRCINFO:INI" \
 			--map-syntax "*install:Bourne Again Shell (bash)" \
diff --git a/src/lib/valid-search.sh b/src/lib/valid-search.sh
new file mode 100644
index 0000000..43799a5
--- /dev/null
+++ b/src/lib/valid-search.sh
@@ -0,0 +1,11 @@
+#!/hint/bash
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+:
+
+# shellcheck disable=2034
+valid_search_output_format=(
+	pretty
+	plain
+	json
+)
-- 
cgit v1.2.3-54-g00ecf

