Serenity Operating System
1/*
2 * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/StringBuilder.h>
28#include <LibCore/ArgsParser.h>
29#include <getopt.h>
30#include <limits.h>
31#include <stdio.h>
32#include <string.h>
33
34namespace Core {
35
36ArgsParser::ArgsParser()
37{
38 add_option(m_show_help, "Display this message", "help", 0);
39}
40
41void ArgsParser::parse(int argc, char** argv)
42{
43 auto print_usage_and_exit = [this, argv] {
44 print_usage(stderr, argv[0]);
45 exit(1);
46 };
47 Vector<option> long_options;
48 StringBuilder short_options_builder;
49
50 int index_of_found_long_option = -1;
51
52 for (size_t i = 0; i < m_options.size(); i++) {
53 auto& opt = m_options[i];
54 if (opt.long_name) {
55 option long_opt {
56 opt.long_name,
57 opt.requires_argument ? required_argument : no_argument,
58 &index_of_found_long_option,
59 static_cast<int>(i)
60 };
61 long_options.append(long_opt);
62 }
63 if (opt.short_name) {
64 short_options_builder.append(opt.short_name);
65 if (opt.requires_argument)
66 short_options_builder.append(':');
67 }
68 }
69 long_options.append({ 0, 0, 0, 0 });
70
71 String short_options = short_options_builder.build();
72
73 while (true) {
74 int c = getopt_long(argc, argv, short_options.characters(), long_options.data(), nullptr);
75 if (c == -1) {
76 // We have reached the end.
77 break;
78 } else if (c == '?') {
79 // There was an error, and getopt() has already
80 // printed its error message.
81 print_usage_and_exit();
82 }
83
84 // Let's see what option we just found.
85 Option* found_option = nullptr;
86 if (c == 0) {
87 // It was a long option.
88 ASSERT(index_of_found_long_option >= 0);
89 found_option = &m_options[index_of_found_long_option];
90 index_of_found_long_option = -1;
91 } else {
92 // It was a short option, look it up.
93 auto it = m_options.find([c](auto& opt) { return c == opt.short_name; });
94 ASSERT(!it.is_end());
95 found_option = &*it;
96 }
97 ASSERT(found_option);
98
99 const char* arg = found_option->requires_argument ? optarg : nullptr;
100 if (!found_option->accept_value(arg)) {
101 fprintf(stderr, "Invalid value for option %s\n", found_option->name_for_display().characters());
102 print_usage_and_exit();
103 }
104 }
105
106 // We're done processing options, now let's parse positional arguments.
107
108 int values_left = argc - optind;
109 int num_values_for_arg[m_positional_args.size()];
110 int total_values_required = 0;
111 for (size_t i = 0; i < m_positional_args.size(); i++) {
112 auto& arg = m_positional_args[i];
113 num_values_for_arg[i] = arg.min_values;
114 total_values_required += arg.min_values;
115 }
116
117 if (total_values_required > values_left)
118 print_usage_and_exit();
119 int extra_values_to_distribute = values_left - total_values_required;
120
121 for (size_t i = 0; i < m_positional_args.size(); i++) {
122 auto& arg = m_positional_args[i];
123 int extra_values_to_this_arg = min(arg.max_values - arg.min_values, extra_values_to_distribute);
124 num_values_for_arg[i] += extra_values_to_this_arg;
125 extra_values_to_distribute -= extra_values_to_this_arg;
126 if (extra_values_to_distribute == 0)
127 break;
128 }
129
130 if (extra_values_to_distribute > 0) {
131 // We still have too many values :(
132 print_usage_and_exit();
133 }
134
135 for (size_t i = 0; i < m_positional_args.size(); i++) {
136 auto& arg = m_positional_args[i];
137 for (int j = 0; j < num_values_for_arg[i]; j++) {
138 const char* value = argv[optind++];
139 if (!arg.accept_value(value)) {
140 fprintf(stderr, "Invalid value for argument %s\n", arg.name);
141 print_usage_and_exit();
142 }
143 }
144 }
145
146 // We're done parsing! :)
147 // Now let's show help if requested.
148 if (m_show_help) {
149 print_usage(stdout, argv[0]);
150 exit(0);
151 }
152}
153
154void ArgsParser::print_usage(FILE* file, const char* argv0)
155{
156 fprintf(file, "Usage:\n\t%s", argv0);
157
158 for (auto& opt : m_options) {
159 if (opt.long_name && !strcmp(opt.long_name, "help"))
160 continue;
161 if (opt.requires_argument)
162 fprintf(file, " [%s %s]", opt.name_for_display().characters(), opt.value_name);
163 else
164 fprintf(file, " [%s]", opt.name_for_display().characters());
165 }
166 for (auto& arg : m_positional_args) {
167 bool required = arg.min_values > 0;
168 bool repeated = arg.max_values > 1;
169
170 if (required && repeated)
171 fprintf(file, " <%s...>", arg.name);
172 else if (required && !repeated)
173 fprintf(file, " <%s>", arg.name);
174 else if (!required && repeated)
175 fprintf(file, " [%s...]", arg.name);
176 else if (!required && !repeated)
177 fprintf(file, " [%s]", arg.name);
178 }
179
180 if (!m_options.is_empty())
181 fprintf(file, "\nOptions:\n");
182
183 for (auto& opt : m_options) {
184 auto print_argument = [&]() {
185 if (opt.value_name) {
186 if (opt.requires_argument)
187 fprintf(file, " %s", opt.value_name);
188 else
189 fprintf(file, " [%s]", opt.value_name);
190 }
191 };
192 fprintf(file, "\t");
193 if (opt.short_name) {
194 fprintf(file, "-%c", opt.short_name);
195 print_argument();
196 }
197 if (opt.short_name && opt.long_name)
198 fprintf(file, ", ");
199 if (opt.long_name) {
200 fprintf(file, "--%s", opt.long_name);
201 print_argument();
202 }
203
204 if (opt.help_string)
205 fprintf(file, "\t%s", opt.help_string);
206 fprintf(file, "\n");
207 }
208
209 if (!m_positional_args.is_empty())
210 fprintf(file, "\nArguments:\n");
211
212 for (auto& arg : m_positional_args) {
213 fprintf(file, "\t%s", arg.name);
214 if (arg.help_string)
215 fprintf(file, "\t%s", arg.help_string);
216 fprintf(file, "\n");
217 }
218}
219
220void ArgsParser::add_option(Option&& option)
221{
222 m_options.append(move(option));
223}
224
225void ArgsParser::add_option(bool& value, const char* help_string, const char* long_name, char short_name)
226{
227 Option option {
228 false,
229 help_string,
230 long_name,
231 short_name,
232 nullptr,
233 [&value](const char* s) {
234 ASSERT(s == nullptr);
235 value = true;
236 return true;
237 }
238 };
239 add_option(move(option));
240}
241
242void ArgsParser::add_option(const char*& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
243{
244 Option option {
245 true,
246 help_string,
247 long_name,
248 short_name,
249 value_name,
250 [&value](const char* s) {
251 value = s;
252 return true;
253 }
254 };
255 add_option(move(option));
256}
257
258void ArgsParser::add_option(int& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
259{
260 Option option {
261 true,
262 help_string,
263 long_name,
264 short_name,
265 value_name,
266 [&value](const char* s) {
267 bool ok;
268 value = StringView(s).to_int(ok);
269 return ok;
270 }
271 };
272 add_option(move(option));
273}
274
275void ArgsParser::add_positional_argument(Arg&& arg)
276{
277 m_positional_args.append(move(arg));
278}
279
280void ArgsParser::add_positional_argument(const char*& value, const char* help_string, const char* name, Required required)
281{
282 Arg arg {
283 help_string,
284 name,
285 required == Required::Yes ? 1 : 0,
286 1,
287 [&value](const char* s) {
288 value = s;
289 return true;
290 }
291 };
292 add_positional_argument(move(arg));
293}
294
295void ArgsParser::add_positional_argument(int& value, const char* help_string, const char* name, Required required)
296{
297 Arg arg {
298 help_string,
299 name,
300 required == Required::Yes ? 1 : 0,
301 1,
302 [&value](const char* s) {
303 bool ok;
304 value = StringView(s).to_int(ok);
305 return ok;
306 }
307 };
308 add_positional_argument(move(arg));
309}
310
311void ArgsParser::add_positional_argument(Vector<const char*>& values, const char* help_string, const char* name, Required required)
312{
313 Arg arg {
314 help_string,
315 name,
316 required == Required::Yes ? 1 : 0,
317 INT_MAX,
318 [&values](const char* s) {
319 values.append(s);
320 return true;
321 }
322 };
323 add_positional_argument(move(arg));
324}
325
326}