summaryrefslogtreecommitdiffstats
path: root/scripts/libmakepkg/util/parseopts.sh.in
blob: 698f2acbabb4a2da81f31633015b72be420411ea (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
#!/bin/bash
#
#   parseopts.sh - getopt_long-like parser
#
#   Copyright (c) 2012-2019 Pacman Development Team <pacman-dev@archlinux.org>
#
#   This program 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 2 of the License, or
#   (at your option) any later version.
#
#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#
# A getopt_long-like parser which portably supports longopts and
# shortopts with some GNU extensions. For both short and long opts,
# options requiring an argument should be suffixed with a colon, and
# options with optional arguments should be suffixed with a question
# mark. After the first argument containing the short opts, any number
# of valid long opts may be be passed. The end of the options delimiter
# must then be added, followed by the user arguments to the calling
# program.
#
# Options with optional arguments will be returned as "--longopt=optarg"
# for longopts, or "-o=optarg" for shortopts. This isn't actually a valid
# way to pass an optional argument with a shortopt on the command line,
# but is done by parseopts to enable the caller script to split the option
# and its optarg easily.
#
# Recommended Usage:
#   OPT_SHORT='fb:zq?'
#   OPT_LONG=('foo' 'bar:' 'baz' 'qux?')
#   if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
#     exit 1
#   fi
#   set -- "${OPTRET[@]}"
# Returns:
#   0: parse success
#   1: parse failure (error message supplied)
parseopts() {
	local opt= optarg= i= shortopts=$1
	local -a longopts=() unused_argv=()

	shift
	while [[ $1 && $1 != '--' ]]; do
		longopts+=("$1")
		shift
	done
	shift

	longoptmatch() {
		local o longmatch=()
		for o in "${longopts[@]}"; do
			if [[ ${o%[:?]} = "$1" ]]; then
				longmatch=("$o")
				break
			fi
			[[ ${o%[:?]} = "$1"* ]] && longmatch+=("$o")
		done

		case ${#longmatch[*]} in
			1)
				# success, override with opt and return arg req (0 == none, 1 == required, 2 == optional)
				opt=${longmatch%[:?]}
				case $longmatch in
					*:)  return 1 ;;
					*\?) return 2 ;;
					*)   return 0 ;;
				esac
				;;
			0)
				# fail, no match found
				return 255 ;;
			*)
				# fail, ambiguous match
				printf "${0##*/}: $(gettext "option '%s' is ambiguous; possibilities:")" "--$1"
				printf " '%s'" "${longmatch[@]%[:?]}"
				printf '\n'
				return 254 ;;
		esac >&2
	}

	while (( $# )); do
		case $1 in
			--) # explicit end of options
				shift
				break
				;;
			-[!-]*) # short option
				for (( i = 1; i < ${#1}; i++ )); do
					opt=${1:i:1}

					case $shortopts in
						# option requires optarg
						*$opt:*)
							# if we're not at the end of the option chunk, the rest is the optarg
							if (( i < ${#1} - 1 )); then
								OPTRET+=("-$opt" "${1:i+1}")
								break
							# if we're at the end, grab the the next positional, if it exists
							elif (( i == ${#1} - 1 && $# > 1 )); then
								OPTRET+=("-$opt" "$2")
								shift
								break
							# parse failure
							else
								printf "${0##*/}: $(gettext "option requires an argument") -- '%s'\n" "$opt" >&2
								OPTRET=(--)
								return 1
							fi
							;;
						# option's optarg is optional
						*$opt\?*)
							# if we're not at the end of the option chunk, the rest is the optarg
							if (( i < ${#1} - 1 )); then
								OPTRET+=("-$opt=${1:i+1}")
								break
							# option has no optarg
							else
								OPTRET+=("-$opt")
							fi
							;;
						# option has no optarg
						*$opt*)
							OPTRET+=("-$opt")
							;;
						# option doesn't exist
						*)
							printf "${0##*/}: $(gettext "invalid option") -- '%s'\n" "$opt" >&2
							OPTRET=(--)
							return 1
							;;
					esac
				done
				;;
			--?*=*|--?*) # long option
				IFS='=' read -r opt optarg <<< "${1#--}"
				longoptmatch "$opt"
				case $? in
					0)
						# parse failure
						if [[ $1 = *=* ]]; then
							printf "${0##*/}: $(gettext "option '%s' does not allow an argument")\n" "--$opt" >&2
							OPTRET=(--)
							return 1
						# --longopt
						else
							OPTRET+=("--$opt")
						fi
						;;
					1)
						# --longopt=optarg
						if [[ $1 = *=* ]]; then
							OPTRET+=("--$opt" "$optarg")
						# --longopt optarg
						elif (( $# > 1 )); then
							OPTRET+=("--$opt" "$2" )
							shift
						# parse failure
						else
							printf "${0##*/}: $(gettext "option '%s' requires an argument")\n" "--$opt" >&2
							OPTRET=(--)
							return 1
						fi
						;;
					2)
						# --longopt=optarg
						if [[ $1 = *=* ]]; then
							OPTRET+=("--$opt=$optarg")
						# --longopt
						else
							OPTRET+=("--$opt")
						fi
						;;
					254)
						# ambiguous option -- error was reported for us by longoptmatch()
						OPTRET=(--)
						return 1
						;;
					255)
						# parse failure
						printf "${0##*/}: $(gettext "invalid option") '--%s'\n" "$opt" >&2
						OPTRET=(--)
						return 1
						;;
				esac
				;;
			*) # non-option arg encountered, add it as a parameter
				unused_argv+=("$1")
				;;
		esac
		shift
	done

	# add end-of-opt terminator and any leftover positional parameters
	OPTRET+=('--' "${unused_argv[@]}" "$@")
	unset longoptmatch

	return 0
}