aboutsummaryrefslogtreecommitdiff
path: root/auria.sh
blob: 1eb9ac58071035129fb10b6749fb8cbea5a5e486 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
#!/bin/bash
#
# auria.sh - the main code of auria, an aur helper.
#
# Copyright 2017, 2018 Einhard Leichtfuß
#
# This file is part of auria.
#
# auria is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# auria 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 Affero General Public License
# along with auria.  If not, see <http://www.gnu.org/licenses/>.
#


# deps:
# - bash
# - pacman
# - jq
# - curl>=7.18.0: for --data-urlencode
# - cmp
# - grep
# - sed
# 
# optdeps:
# - ed: default editor
#


# PROBLEM: The AUR RPC interface considers depends_i686 and similar as
#          depends (or similar).
# PROBLEM: The AUR RPC does not search for provides (e.g. dictd-foldoc).
# PROBLEM/NOPROBLEM: Things may change while downloading things (e.g. deps).
# TODO: Fix update_all
#      `- Do not update if AUR version and local version match.
# TODO: search: append (group) [installed{ :verstring}] if applicable
#      `- group not easily available;
#      `- installed is probably time consuming - alpm could be used.
# TODO: Use guess_devel.
# TODO: Differentiate errors and failures in return codes (mostly done).
# TODO: Conflicts.
# TODO: Find packages only providing a dep (e.g. commonist->java-environment)
#      `- apparently impossible via AUR RPC.
# TODO: (print_localver) case pkg is only provided.
# TODO: print_repover may print several results (e.g. parabola/linux-libre).
#      `- print_localver probably has the same issue.
# TODO: Use inform/note/subinform properly.
# TODO: Remove package from repo_deps, if it later gets added to the aur
#       lists.
# TODO: Be consistent in the usage of test / [.] / [[.]].
# TODO: (info) parse properly (parse.c)
# TODO: Prevent asking for password in case of a --needed with effect.
#      `- --needed should no longer be necessary, once update_all is fixed.
# TODO: Handle failures of single packages in update_all.
#      `- Alternatively, offer a variable skip_updates[] or so.
# TODO: Options, e.g. to only build or to selectively update.
#      `- Might be (partially) solved by passing args to makepkg.
# TODO: Do not attempt to build an already built packages. Or else makepkg
#       unhappily fails.
#      `- Alternatively, use makepkg's -f option.
# CONSIDER: Use makepkg_options=(--cleanbuild --syncdeps) as default.
# CONSIDER: (repo_deps) array vs. (newline separated string)
# CONSIDER: (repo_deps) (space vs. newline) separated string
# CONSIDER: (print_localver,print_repover) removal
# CONSIDER: Use `local var=value' at beginning of functions.
# CONSIDER: Remove repo_deps[].
# CONSIDER: Inform about what packages will be installed.
# CONSIDER: Newlines.
# CONSIDER: Differentiate dep and build-dep.
#          `- Option to remove build-deps after installation.
# CONSIDER: Counter in install_list()
# CONSIDER: Check for matching architecture before attempting to build.
# Q?: When to set retstr to ''. 
# IDEA: (print_restriction) consider to use sed.
#
# NOTE: (jq) `// empty' converts null to empty, which becomes "".


# Default options (must be kept in sync with the distributed version of the
# global configuration file).
typeset global_conffile=/etc/auria_conf.sh
typeset local_conffile="$HOME/.config/auria_conf.sh"
typeset git_dir="$HOME/aur"
typeset -a makepkg_options=()
typeset pkgext=.pkg.tar.xz	# Should be the same as PKGEXT in `makepkg.conf'.
typeset arch="$(uname -m)"
typeset color=false
typeset editor=ed
typeset diff_program=diff
typeset yes_pkgs=()
typeset -a devel_pkgs=()
typeset -i curl_connect_timeout=5
typeset -i curl_max_time=20

test -n "$EDITOR" && editor="$EDITOR"

# Source configuration files.
test -f "$global_conffile" && . "$global_conffile"
test -f "$local_conffile" && . "$local_conffile"

shopt -s expand_aliases

alias parse=./parse

typeset -a curl_args=(
	--connect-timeout $curl_connect_timeout
	--max-time $curl_max_time
)

alias qpacman='pacman > /dev/null 2>&1'

typeset nocolor bviolet bwhite bgreen bred byellow
typeset nbviolet nbwhite nbgreen nbred nbyellow
if $color
then
	nocolor=$'\e[0m'
	bviolet=$'\e[1;35m'
	bwhite=$'\e[1;38m'
	bgreen=$'\e[1;32m'
	bred=$'\e[1;31m'
	byellow=$'\e[1;33m'
	bblue=$'\e[1;34m'
	nbviolet="${nocolor}${bviolet}"
	nbwhite="${nocolor}${bwhite}"
	nbgreen="${nocolor}${bgreen}"
	nbred="${nocolor}${bred}"
	nbyellow="${nocolor}${byellow}"
	nbblue="${nocolor}${bblue}"
else
	makepkg_options+=(--no-color)
fi

# return string of the last function returning a string.
typeset retstr

typeset -a repo_deps			# dependencies in official repositories.
typeset -A aur_deps				# aur deps for each aur package (space sep.).
typeset -A aur_revdeps		# aur pkgs depending on an pkg, for each.
typeset -A aur_itype 			# explicit / dep / update / ""
typeset -A aur_pkgbase
typeset -A aur_pkgver
typeset -a aur_pkgorder		# properly ordered array of to be installed pkgs.

# note: Only for debugging purposes.
function reset_vars
{
	repo_deps=()
	aur_deps=()
	aur_revdeps=()
	aur_itype=()
	aur_pkgbase=()
	aur_pkgver=()
	aur_pkgorder=()
}

typeset tmp
function cleanup
{
	test -n "$tmp" && test -d "$tmp" && rm -rf "$tmp"
}
trap cleanup EXIT

function main
{
	prepare || return $?

	case "$1" in
		update)
			update_all;;
		install)
			install_full "$2";;
		search)
			search "${@:2}";;
		rpc)
			# For debugging.
			rpc "${@:2}";;
		*)
			error "Functionality ${1} not implemented."
			return 2
	esac

	local ret=$?
	cleanup
	return $?
}

function prepare
{
	if test -e "$git_dir"
	then
		if ! test -d "$git_dir"
		then
			error "${git_dir} is not a directory."
			return 2
		fi
	else
		if ! mkdir "$git_dir"
		then
			error "Failed to create ${git_dir}."
			return 2
		fi
	fi

	tmp="$(mktemp -d || return 4)"
	#CARCH="$(uname -m)"
}

# $1 package: name [. pkgver-comparator . version string]
# interactive (<- install_full).
function update
{
	install_full "$1" update
}

# $1 package: name [. pkgver-comparator . version string]
# interactive (<- resolve_deps, install_list).
function install_full
{
	local pkg itype

	test -z "$1" && return 4
	pkg="$1"

	if [[ "$2" == update ]]
	then
		itype=update
		inform "Start update procedure for ${pkg}."
	else
		itype=explicit
		inform "Start install procedure for ${pkg}."
	fi

	#inform "Fetching package information from AUR..."
	subinform "Resolving dependencies..."
	resolve_deps "$pkg" $itype '' || return $?
	echo
	make_pkgorder || return $?

	install_list
}

# Installs all packages in ${aur_pkgorder[@]}, in order.
# interactive (<- present_files).
function install_list
{
	local pkgbase old_pkgbase_exists pkgver pkgrel epoch pkg_arch version \
		package

	for pkg in "${aur_pkgorder[@]}"
	do
		pkgbase="${aur_pkgbase[$pkg]}"

		if [[ "${aur_itype[$pkg]}" == update ]]
		then
			inform "Update ${pkg}..."
		else
			inform "Install ${pkg}..."
		fi

		# Get sources and cd into $aur_root/$pkgbase.
		get_sources "$pkgbase" || return $?
		old_pkgbuild_exists=$retstr

		if ! test -f PKGBUILD
		then
			error "PKGBUILD does not exist."
			return 2
		fi

		if ! match "$pkg" "${yes_pkgs[@]}"
		then
			present_files $old_pkgbuild_exists
			ask y "Continue" || return 0
		fi

		# Build package.
		# makepkg's -i flag cannot handle split packages, hence installing has
		# to be done separately.
		makepkg --syncdeps "${makepkg_options[@]}" || return 2
		echo

		# Get version and other data (version may have changed due to pkgver()).
		makepkg --printsrcinfo > "${tmp}/srcinfo"
		pkgver="$(grep -E '^\s*pkgver' "${tmp}/srcinfo" | sed -E 's/^.*=\s*//')"
		pkgrel="$(grep -E '^\s*pkgrel' "${tmp}/srcinfo" | sed -E 's/^.*=\s*//')"
		epoch="$( grep -E '^\s*epoch'  "${tmp}/srcinfo" | sed -E 's/^.*=\s*//')"

		# Get the package's architecture.
		# If neither any nor $arch were supported, makepkg would've failed
		# (unless makepkg is configured to build for a different architecture
		#  than $arch, which should not be the case).
		if grep -qE '^\s*arch\s*=\s*any$' < "${tmp}/srcinfo"
		then
			pkg_arch=any
		else
			pkg_arch="$arch"
		fi

		# Compose the package's version.
		if test -n "$epoch"
		then
			version="${epoch}:${pkgver}-${pkgrel}"
		else
			version="${pkgver}-${pkgrel}"
		fi

		# Install package.
		inform "Install built package..."
		package="${pkg}-${version}-${pkg_arch}${pkgext}"
		if [[ "${aur_itype[$pkg]}" == dep ]]
		then
			sudo pacman -U --asdeps "$package" || return 2
		else
			sudo pacman -U --needed "$package" || return 2
		fi

		echo
	done
}

# OLD.
# $1 package: name
# interactive (<- get_pkgbase, present_files).
function install_single
{
	local pkg pkgbase ver localver

	test -z "$1" && return 4
	pkg="$1"

	#if [[ "$2" == update ]]
	#then
		#inform "Start update procedure for ${pkg}."
	#else
		#inform "Start install procedure for ${pkg}."
	#fi

	inform "Fetching package information from AUR..."
	#inform "Resolving dependencies..."
	subinform " ${pkg}"
	rpc info "${tmp}/json" loud "$*" || return $?

	get_pkgbase "$pkg" "$pkg" "${tmp}/json" explicit || return $?
	test -z "$retstr" && return 0; local -i i=$retstr
	pkgbase="$(jq -er ".results[$i].PackageBase" "${tmp}/json" || return 4)"

	get_sources "$pkgbase" || return $?
	local old_pkgbuild_exists=$retstr

	if ! test -f PKGBUILD
	then
		error "PKGBUILD does not exist."
		return 2
	fi

	if ! match "$pkg" "${yes_pkgs[@]}"
	then
		present_files $old_pkgbuild_exists
		ask y "Continue" || return 0
	fi

	makepkg "${makepkg_options[@]}" -i || return 2
}

# $1 package: name
# $2 package: name [. pkgver-comparator . version string]
# $3 json file (info): filename
# $4 installation type: (dep|explicit|update)
# $retstr result: numeric id {on success}
# interactive {if several pkgbases match}.
function get_pkgbase
{
	retstr=''
	local pkg="$1"
	local pkgstr="$2"
	local json="$3"
	local itype=$4

	local -i rcount="$(jq -er '.resultcount' "$json" || return 4)"
	local -i i
	if [[ $rcount -eq 0 ]]
	then
		if [[ "$itype" == dep ]]
		then
			error "Dependency ${pkgstr} could not be resolved."
		else
			error "[RPC] No result for ${pkg} found."
		fi
		return 2
	elif [[ $rcount -gt 1 ]]
	then
		# Should never happen.
		inform "Several package bases match:"
		i=0
		while [[ $i -lt $rcount ]]
		do
			subinform " [$i] $(jq -er \
			 	".results[$i] | \"\\(.PackageBase) (\\(.Version))\"" "$json" \
				|| return 4)"
		done

		while true
		do
			ask_general "" "0-$((rcount-1))/n" "Which one do you want"

			if [[ "$retstr" =~ ^[0-9]*$ ]]
			then
				(( $retstr >= 0 && $retstr < $rcount )) && break
			elif [[ "${retstr,,}" =~ ^no?$ ]]
			then
				retstr=''
				break
			fi
		done
	else
		retstr=0
	fi
}

# $1 pkgbase: name
# $retstr old_pkgbuild_exists: boolean {on success}
# action: cd ${git_dir}/${pkgbase} {on success}
function get_sources
{
	retstr=''
	local pkgbase="$1"

	local old_pkgbuild_exists=false
	if test -d "${git_dir}/${pkgbase}"
	then
		cd "${git_dir}/${pkgbase}" || return 4
		if test -f PKGBUILD
		then
			cp PKGBUILD "${tmp}/PKGBUILD" || return 4
			old_pkgbuild_exists=true
		fi
		subinform "Pulling updates from AUR..."
		git pull || return 4
	else
		cd "$git_dir" || return 4
		subinform "Cloning sources from AUR..."
		git clone "https://aur.archlinux.org/${pkgbase}.git" "${pkgbase}" \
			|| return 4
		cd "${pkgbase}" || return 4
	fi

	echo

	retstr=$old_pkgbuild_exists
}

# $1 old_pkgbuild_exists: boolean
# $2 package: name
# requires: $PWD == ${git_dir}/${pkgbase}
# requires: ${tmp}/PKGBUILD (if $old_pkbuild_exists)
# interactive.
function present_files
{
	local old_pkgbuild_exists pkgname
	old_pkgbuild_exists=$1
	pkgname="$2"

	if $old_pkgbuild_exists
	then
		if cmp "${tmp}/PKGBUILD" PKGBUILD
		then
			ask n "PKGBUILD did not change. View anyway" && "$editor" PKGBUILD
		else
			ask y "PKGBUILD has changed. View diff" \
				&& "$diff_program" "${tmp}/PKGBUILD" PKGBUILD
			ask n "View the whole file" && "$editor" PKGBUILD
		fi
	else
		ask y "View the PKGBUILD" && "$editor" PKGBUILD
	fi

	if test -f "${pkgname}.install"
	then
		ask_general "${pkgname}.install" '' "View other file"
	else
		ask_general '' '' "View other file"
	fi
	local file="$retval"

	while test -n "$file"
	do
		"$editor" "$file"
		file="$(ask_general '' '' "View other file")"
	done
}

# note: Any call for explicit packages must be made before any call for
#       non-explicit ones.
# $1 package: name [. pkgver-comparator . version string]
# $2 installation type: (dep|explicit|update)
# $3 direct reverse dependency (caller): (name|)
# interactive (<- get_pkgbase).
function resolve_deps
{
	local itype pkgstr pkg pkgbase pkgver rst rst_kind rst_ver existed new \
		abovepkg

	pkgstr="$1"
	itype=$2
	abovepkg="$3"

	# Check for version restriction.
	rst=( $(print_restriction "$pkgstr") )
	pkg="${rst[0]}"
	rst_kind="${rst[1]}"
	rst_ver="${rst[2]}"

	# Search locally and in the repos first (for deps and upon update).
	# This does also populate the $repo_deps array.
	if [[ "$itype" == dep ]]
	then
		depsearch_noaur "$pkg" "$pkgstr" && return 0
	elif [[ "$itype" == update ]]
	then
		if depsearch_noaur "$pkg" "$pkgstr"
		then
			inform "$pkgstr exists in the regular repositories."
			ask n "Try to update from AUR anyways" || return 0
		fi
	fi

	# Search the AUR.
	if [[ -n "${aur_itype["$pkg"]}" ]]
	then
		new=false
		pkgver="${aur_pkgver["$pkg"]}"
	else
		subinform " $pkgstr"
		new=true
		rpc info "${tmp}/json" quiet "$pkg" || return $?
		if [[ -n "$retstr" ]]
		then
			if [[ "$itype" == dep ]]
			then
				error "Dependency ${pkgstr} could not be resolved."
			else
				error "[RPC] ${retstr}"
			fi
			return 2
		fi

		get_pkgbase "$pkg" "$pkgstr" "${tmp}/json" $itype || return $?
		test -z "$retstr" && return 1; local -i i=$retstr
		pkgbase="$(jq -er ".results[$i].PackageBase" "${tmp}/json" || return 4)"
		aur_pkgbase["$pkg"]="$pkgbase"
		pkgver="$(jq -er ".results[$i].Version" "${tmp}/json" || return 4)"
		aur_pkgver["$pkg"]="$pkgver"

		aur_itype["$pkg"]=$itype
	fi

	if ! cmp_ver "$pkgver" "$rst_ver" "$rst_kind"
	then
		error "Dependency ${pkgstr} could not be resolved."
		return 2
	fi

	# Add edges to the tree if the dep will be taken from the AUR.
	if test -n "$abovepkg"
	then
		aur_deps["$abovepkg"]+=" $pkg "
		aur_revdeps["$pkg"]+=" $abovepkg "
	fi

	if $new
	then
		local deps makedeps
		deps=( $(jq -r ".results[$i].Depends[]?" "${tmp}/json" \
			|| return 4) )
		makedeps=( $(jq -r ".results[$i].MakeDepends[]?" "${tmp}/json" \
			|| return 4) )

		# Recurse.
		local dep
		for dep in "${deps[@]}" "${makedeps[@]}"
		do
			resolve_deps "$dep" dep "$pkg" || return $?
		done

		if test -z "${aur_deps["$pkg"]}"
		then
			aur_pkgorder+=("$pkg")
		fi
	fi
}

# Requires prepare_sources() to be run and its results unaltered.
function make_pkgorder
{
	local pkg

	if test -z "${aur_pkgorder[0]}"
	then
		error "Circular dependency found."
		return 2
	fi


	## Go up the reverse tree from bottom to top.

	local -i i=0
	while test -n "${aur_pkgorder[$i]}"
	do
		pkg="${aur_pkgorder[$i]}"

		# Remove the resolved package off the dependency tree.
		# Note, that the reverse dependencies do not require removal.
		for revdep in ${aur_revdeps["$pkg"]}
		do
			aur_deps["$revdep"]="${aur_deps["$revdep"]/ $pkg /}"
			if test -z "${aur_deps["$revdep"]}"
			then
				aur_pkgorder+=("$revdep")
			fi
		done
		i+=1
	done

	if test -n "${aur_deps["$pkg"]}"
	then
		error "Circular dependency found."
		return 2
	fi
}

# OLD.
function install_deptree
{
	local pkg

	# Use $@ in order to be able to use shift.
	set "${aur_pkgorder[@]}"

	while [ $# -ne 0 ]
	do
		# Pop.
		pkg="$1"; shift

		# TODO: install $pkg

		# Remove the resolved package off the dependency tree.
		# Note, that the reverse dependencies do not require removal.
		for revdep in ${aur_revdeps["$pkg"]}
		do
			aur_deps["$revdep"]="${aur_deps["$revdep"]/ $pkg /}"
			if test -z "${aur_deps["$revdep"]}"
			then
				aur_pkgorder+=("$revdep")
			fi
		done
	done
}

# $1 package: name
# $2 package: name [. pkgver-comparator . version string]
function depsearch_noaur
{
	local pkg pkgstr
	pkg="$1"
	pkgstr="$2"

	# In case of no dep restriction, look up in list first.
	[[ "$pkg" == "$pkgstr" ]] && match "$pkg" "${repo_deps[@]}" && return 0

	# Search locally.
	qpacman -T "$pkgstr" && return 0

	# Search in the repos.
	qpacman -Sp "$pkgstr" \
		&& { match "$pkg" "${repo_deps[@]}" || repo_deps+=("$pkg"); } \
		&& return 0

	return 1
}

# $1 version 1: [version string] {required if -n $comparator}
# $2 version 2: [version string] {required if -n $comparator}
# $3 comparator: [(-gt|-lt|-ge|-le|-eq)]
# returns result: (0|1)
function cmp_ver
{
	test -z "$3" && return 0
	local -i res=$(vercmp "$1" "$2")
	eval "[[ ${res} ${3} 0 ]]"
	return $?
}

# $1 package: name [. pkgver-comparator . version string]
# prints package and restriction: name + (-gt|-lt|-ge|-le|-eq)
#                                 + version string
function print_restriction
{
	local v
	v="${1##*<=}"; test "$v" != "$1" && printf "%s\n" "${1%<=*} -le ${v}" \
		&& return 0
	v="${1##*<}"; test "$v" != "$1" && printf "%s\n" "${1%<*} -lt ${v}" \
		&& return 0
	v="${1##*>=}"; test "$v" != "$1" && printf "%s\n" "${1%>=*} -ge ${v}" \
		&& return 0
	v="${1##*>}"; test "$v" != "$1" && printf "%s\n" "${1%>*} -gt ${v}" \
		&& return 0
	v="${1##*=}"; test "$v" != "$1" && printf "%s\n" "${1%=*} -eq ${v}" \
		&& return 0

	printf "%s\n" "$1"
}

# $1 package: name
# prints local version: version string
function print_localver
{
	pacman -Rdd --print-format "%v" "$1"
}

# $1 package: name
# prints repository version: version string
function print_repover
{
	pacman -Sdd --print-format "%v" "$1"
}

# $1 package: name
# returns guess: (0|1)
function guess_vcs
{
	[[ "$1" =~ -(git|svn|hg|bzr|cvs|fossil|darcs|rcs|arch|mtn)$ ]]
}

# interactive (<- resolve_deps, install_list).
function update_all
{
	local pkg

	inform "Start update of all AUR packages."

	for pkg in $(pacman -Qmq)
	do
		#qpacman -T "$pkg" && continue
		subinform "Resolving dependencies for ${pkg}..."
		resolve_deps "$pkg" update '' || return $?
	done
	echo

	inform "Flatten dependency tree to a list..."
	make_pkgorder || return $?
	echo

	install_list
}

# OLD.
# interactive (<- update).
function update_all_old
{
	local pkg

	for pkg in $(pacman -Qmq)
	do
		update "$pkg"
	done
}

function inform
{
	printf "${nbblue}::${nocolor} ${bwhite}%s${nocolor}\n" "$*" >&2
}

function subinform
{
	printf "%s\n" "$*"
}

function note
{
	printf "${nbwhite}%s${nocolor}\n" "$*" >&2
}

function warn
{
	printf "${nbyellow}warning:${nocolor} %s\n" "$*" >&2
}

function error
{
	printf "${nbred}error:${nocolor} %s\n" "$*" >&2
}

# $1 query type: (info | search:(name|name-desc|maintainer)),
# $2 output file: filename
# $3 sound: (quiet|loud)
# ${@:4} args: (string)+
# $retstr error: string {on failure if $quiet}
function rpc
{
	retstr=''
	# According to the website (/rpc/), arg should be subsituted by arg[]
	# if the query type is info.

	local qtype search_by quiet args
	qtype=${1%:*}
	search_by=${1#*:}
	file="$2"
	test "$3" = quiet && quiet=true || quiet=false
	args=( "${@:4}" )

	local -a cargs
	if [[ "$qtype" == info ]]
	then
		for arg in "${args[@]}"
		do
			cargs+=(--data-urlencode "arg[]=${arg}")
		done
	else
		cargs=(--data "search_by=${search_by}" --data-urlencode "arg=${args[0]}")
	fi

	curl "${curl_args[@]}" --silent --get "${cargs[@]}" \
		"https://aur.archlinux.org/rpc/?v=5&type=${qtype}" \
		-o "$file" \
		|| { error "Failed to query AUR RPC interface."; return 2; }

	if [[ "$(jq -er '.type' "$file" || return 4)" == error ]]
	then
		if $quiet
		then
			retstr="$(jq -er '.error' "$file")"
		else
			error "[RPC] $(jq -er '.error' "$file")"
			return 2
		fi
	fi
}

# $* match conditions: (string)+
# prints search results: formatted string
function search
{
	rpc search:name-desc "${tmp}/json" loud "$*" || return $?

	jq -er '.results[] | "\(.Name) \(.Version) \(.Description)"' \
		"${tmp}/json" \
		| parse 4 0 $($color && echo color)
}

# $* packages: (string)+
# prints info results: formatted string
function info
{
	rpc info "${tmp}/json" loud "$@" || return $?

	local -a lines
	lines=(
		"Name"						".Name // empty"
		"Package Base"		".PackageBase // empty"
		"Version"					".Version // empty"
		"Description"			".Description // empty"
		"URL"							".URL // empty"
		"Votes"						".NumVotes // empty"
		"Popularity"			".Popularity // empty"
		"Out Of Date"			".OutOfDate // empty"
		"Maintainer"			".Maintainer // empty"
		"Licenses"				".License[]?"
		"Groups"					".Group[]?"
		"Provides"				".Provides[]?"
		"Depends On"			".Depends[]?"
		"Make Deps"				".MakeDepends[]?"
		"Optional Deps"		".OptDepends[]?"
		"Conflicts With"	".Conflicts[]?"
		"Replaces"				".Replaces[]?"
	)

	jq -er ".results[] | $(printf -- '"--%s:", %s,' "${lines[@]}") empty" \
		"${tmp}/json"
}

# $1 default answer: (y|n|x)
# $2 question: string
# returns yes / no: (0|1)
# interactive.
# Note, that ^D is considered as a default answer.
function ask
{
	# -l auto-converts everything to lower case.
	local -l default
	local question
	default=$1
	question="$2"

	while true
	do
		if [[ "$default" == "y" ]]
		then
			printf "${nbblue}::${nocolor} ${bwhite}%s [Y/n]?${nocolor} " \
				"$question"
		elif [[ "$default" == "n" ]]
		then
			printf "${nbblue}::${nocolor} ${bwhite}%s [y/N]?${nocolor} " \
				"$question"
		else
			printf "${nbblue}::${nocolor} ${bwhite}%s [y/n]?${nocolor} " \
				"$question"
			default="x"
		fi

		local -l reply	# -l converts to lowercase
		read reply

		if [[ "$reply" =~ ^y(es)?$ ]]
		then
			return 0
		elif [[ "$reply" =~ ^no?$ ]]
		then
			return 1
		elif [[ "$default" == "x" || "$reply" != "" ]]
		then
			continue
		elif [[ "$default" == "y" ]]
		then
			return 0
		else # "$default" == "n"
			return 1
		fi
	done
}

# $1 default answer: string
# $2 options: string
# $3 question: string
# $retstr answer: string
# interactive.
function ask_general
{
	retstr=''
	local default options question
	default="$1"
	options="$2"
	question="$3"

	if test -z "$opts"
	then
		if test -z "$default"
		then
			options='...'
		else
			options="${default}/..."
		fi
	fi

	local reply
	printf "${nbblue}::${nocolor} ${bwhite}%s [%s]?${nocolor} " "$question" \
		"$options"
	read reply

	test -n "$reply" && retstr="$reply" || retstr="$default"
}

# $1 needle: string
# ${@:2} haystack: (string)*
# returns yes / no: (0|1)
function match
{
	printf "%s\n" "${@:2}" | grep -q "^${1}$"
}

main "$@"