query: do not exit 1 on empty output
[aurutils.git] / lib / aur-query
bloba1bec89e3b43b4256744818655caafa496943e91
1 #!/bin/bash
2 # aur-query - interface with AurJson
3 [[ -v AUR_DEBUG ]] && set -o xtrace
4 argv0=query
5 AUR_LOCATION=${AUR_LOCATION:-https://aur.archlinux.org}
6 AUR_QUERY_PARALLEL=${AUR_QUERY_PARALLEL:-0}
7 AUR_QUERY_PARALLEL_MAX=${AUR_QUERY_PARALLEL_MAX:-15}
8 AUR_QUERY_RPC=${AUR_QUERY_RPC:-$AUR_LOCATION/rpc}
9 AUR_QUERY_RPC_POST=${AUR_QUERY_RPC_POST:-1}
10 AUR_QUERY_RPC_SPLITNO=${AUR_QUERY_RPC_SPLITNO:-150}
11 AUR_QUERY_RPC_SPLITNO_POST=${AUR_QUERY_RPC_SPLITNO_POST:-500}
12 AUR_QUERY_RPC_VERSION=${AUR_QUERY_RPC_VERSION:-5}
13 PS4='+(${BASH_SOURCE}:${LINENO}):${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
15 # default arguments
16 curl_args=(-A aurutils -fgLsSq)
18 # Filters for generating curl configuration
19 rpc_info() {
20 local rpc_url="$AUR_QUERY_RPC?v=$AUR_QUERY_RPC_VERSION&type=info"
21 local splitno="$AUR_QUERY_RPC_SPLITNO"
23 # Write opening and closing quotes with \x22 (hexadecimal)
24 awk -v rpc="$rpc_url" -v splitno="$splitno" '{
25 if ((NR-1) % splitno == 0) {
26 if (NR > 1)
27 printf "\x22\n"
28 printf "output \x22args-%s-%s\x22\n", NR-1, (NR-1)+splitno
29 printf "url \x22%s&arg[]=%s", rpc, $0
30 } else if (NR > 1) {
31 printf "&arg[]=%s", $0
33 } END {
34 if (NR != 0)
35 printf "\x22\n"
39 # POST requests are useful for info-type requests to circumvent limits on the
40 # URL length with GET requests. The difference is that all data is now included
41 # in the message body, instead of in the URI.
42 rpc_info_post() {
43 local rpc_url="$AUR_QUERY_RPC"
44 local rpc_ver="$AUR_QUERY_RPC_VERSION"
45 local splitno="$AUR_QUERY_RPC_SPLITNO_POST"
47 # Write opening and closing quotes with \x22 (hexadecimal)
48 awk -v rpc="$rpc_url" -v version="$rpc_ver" -v splitno="$splitno" '{
49 if ((NR-1) % splitno == 0) {
50 printf "next\n"
51 printf "output \x22args-%s-%s\x22\n", NR-1, (NR-1)+splitno
52 printf "url \x22%s\x22\n", rpc
53 printf "data \x22v=%s\x22\n", version
54 printf "data \x22type=info\x22\n"
56 printf "data-urlencode \x22arg[]=%s\x22\n", $0
60 # Search-type requests can only contain one package argument, and GET requests
61 # are sufficient for this.
62 rpc_search() {
63 local rpc_url="$AUR_QUERY_RPC?v=$AUR_QUERY_RPC_VERSION&type=search&by=$1&arg"
65 while IFS= read -r term; do
66 printf 'url "%s=%s"\n' "$rpc_url" "$term"
67 printf 'output "arg-%s"\n' "$term"
68 done
71 trap_exit() {
72 if [[ ! -v AUR_DEBUG ]]; then
73 rm -rf -- "$tmp"
74 else
75 printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$tmp"
79 usage() {
80 printf 'usage: %s [-t [info|search] ] [-b by] <package...>\n' "$argv0" >&2
81 exit 1
84 source /usr/share/makepkg/util/parseopts.sh
86 opt_short='b:t:r'
87 opt_long=('by:' 'type:' 'any' 'raw')
88 opt_hidden=('dump-options' 'dump-curl-config')
90 if ! parseopts "$opt_short" "${opt_long[@]}" "${opt_hidden[@]}" -- "$@"; then
91 usage
93 set -- "${OPTRET[@]}"
95 unset arg_by arg_type multiple dump_curl_config
96 while true; do
97 case "$1" in
98 --any) # set union
99 multiple=union ;;
100 -r|--raw)
101 multiple=none ;;
102 -b|--by)
103 shift; arg_by=$1 ;;
104 -t|--type)
105 shift; arg_type=$1 ;;
106 --dump-curl-config)
107 dump_curl_config=1 ;;
108 --dump-options)
109 printf -- '--%s\n' "${opt_long[@]}" ${AUR_DEBUG+"${opt_hidden[@]}"}
110 printf -- '%s' "${opt_short}" | sed 's/.:\?/-&\n/g'
111 exit ;;
112 --) shift; break ;;
113 esac
114 shift
115 done
117 # shellcheck disable=SC2174
118 mkdir -pm 0700 "${TMPDIR:-/tmp}/aurutils-$UID"
119 tmp=$(mktemp -d --tmpdir "aurutils-$UID/$argv0.XXXXXXXX") || exit
120 trap 'trap_exit' EXIT
122 if (( ! $# )); then
123 usage
126 # set filters
127 if [[ $arg_type == "search" ]]; then
128 curl_config() { jq -R -r '@uri' | rpc_search "${arg_by:-name-desc}"; }
130 elif [[ $arg_type == "info" ]] && (( AUR_QUERY_RPC_POST )); then
131 curl_config() { rpc_info_post; } # URI encoding is handled by curl --data-urlencode
133 elif [[ $arg_type == "info" ]]; then
134 curl_config() { jq -R -r '@uri' | rpc_info; }
137 # generate curl configuration
138 if (( $# == 1 )) && [[ $1 == "-" || $1 == "/dev/stdin" ]]; then
139 tee # noop
140 else
141 printf '%s\n' "$@"
142 fi | curl_config >"$tmp"/config
144 # easy access to curl config for debugging
145 if (( ${dump_curl_config-0} )); then
146 cat "$tmp"/config
147 exit
150 # exit cleanly on empty input (#706)
151 if [[ ! -s $tmp/config ]]; then
152 exit
155 # Set intersection only applies to search queries, where one search term leads
156 # to multiple results. This is the default, unless --any is specified. Info
157 # queries are already implicitly a union of terms (packages), unless duplicate
158 # packages were specified, and are so not processed further.
159 multiple=${multiple:-section}
161 if [[ $arg_type == "info" ]]; then
162 multiple=none
165 # set operations on search output
166 case $multiple in
167 section)
168 combine() { jq -Mrcs '(length - 1) as $n
169 | [map(.results | unique[]) | group_by(.)[][$n] | objects] as $r
170 | (.[0] + {
171 "resultcount": ($r | length),
172 "results": $r
174 } ;;
175 union)
176 combine() { jq -Mrcs '([.[].results] | add | unique) as $r
177 | (.[0] + {
178 "resultcount": ($r | length),
179 "results": $r
181 } ;;
182 none)
183 combine() { tee; } ;;
184 esac
186 # support parallel transfers (curl >7.66.0)
187 if (( AUR_QUERY_PARALLEL )); then
188 # curl 7.77.0: tool_operate: don't discard failed parallel transfer result
189 curl_args+=(--parallel --parallel-max "$AUR_QUERY_PARALLEL_MAX" --fail-early)
192 # requests are stored in a subdirectory to avoid problems from the random
193 # ordering of curl --parallel output (#925)
194 mkdir "$tmp"/output
195 env -C "$tmp"/output curl "${curl_args[@]}" -K "$tmp"/config || exit
197 files=("$tmp"/output/*)
198 if [[ ! -f ${files[0]} ]]; then
199 exit 1
202 # check responses for errors (#257)
203 for f in "${files[@]}"; do
204 f_count=$(jq -sr 'map(.resultcount) | add' "$f")
205 f_error=$(jq -r '.error' "$f")
207 if (( ! f_count )) && [[ $f_error != 'null' ]]; then
208 cat "$f" # type=error
209 exit 2
211 done
213 # concatenate results
214 cat "${files[@]}" | combine
216 # vim: set et sw=4 sts=4 ft=sh: