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
33namespace Core {
34
35ArgsParser::ArgsParser()
36{
37 add_option(m_show_help, "Display this message", "help", 0);
38}
39
40void ArgsParser::parse(int argc, char** argv)
41{
42 auto print_usage_and_exit = [this, argv] {
43 print_usage(stderr, argv[0]);
44 exit(1);
45 };
46 Vector<option> long_options;
47 StringBuilder short_options_builder;
48
49 int index_of_found_long_option = -1;
50
51 for (size_t i = 0; i < m_options.size(); i++) {
52 auto& opt = m_options[i];
53 if (opt.long_name) {
54 option long_opt {
55 opt.long_name,
56 opt.requires_argument ? required_argument : no_argument,
57 &index_of_found_long_option,
58 static_cast<int>(i)
59 };
60 long_options.append(long_opt);
61 }
62 if (opt.short_name) {
63 short_options_builder.append(opt.short_name);
64 if (opt.requires_argument)
65 short_options_builder.append(':');
66 }
67 }
68 long_options.append({ 0, 0, 0, 0 });
69
70 String short_options = short_options_builder.build();
71
72 while (true) {
73 int c = getopt_long(argc, argv, short_options.characters(), long_options.data(), nullptr);
74 if (c == -1) {
75 // We have reached the end.
76 break;
77 } else if (c == '?') {
78 // There was an error, and getopt() has already
79 // printed its error message.
80 print_usage_and_exit();
81 }
82
83 // Let's see what option we just found.
84 Option* found_option = nullptr;
85 if (c == 0) {
86 // It was a long option.
87 ASSERT(index_of_found_long_option >= 0);
88 found_option = &m_options[index_of_found_long_option];
89 index_of_found_long_option = -1;
90 } else {
91 // It was a short option, look it up.
92 auto it = m_options.find([c](auto& opt) { return c == opt.short_name; });
93 ASSERT(!it.is_end());
94 found_option = &*it;
95 }
96 ASSERT(found_option);
97
98 const char* arg = found_option->requires_argument ? optarg : nullptr;
99 if (!found_option->accept_value(arg)) {
100 fprintf(stderr, "Invalid value for option %s\n", found_option->name_for_display().characters());
101 print_usage_and_exit();
102 }
103 }
104
105 // We're done processing options, now let's parse positional arguments.
106
107 int values_left = argc - optind;
108 int num_values_for_arg[m_positional_args.size()];
109 int total_values_required = 0;
110 for (size_t i = 0; i < m_positional_args.size(); i++) {
111 auto& arg = m_positional_args[i];
112 num_values_for_arg[i] = arg.min_values;
113 total_values_required += arg.min_values;
114 }
115
116 if (total_values_required > values_left)
117 print_usage_and_exit();
118 int extra_values_to_distribute = values_left - total_values_required;
119
120 for (size_t i = 0; i < m_positional_args.size(); i++) {
121 auto& arg = m_positional_args[i];
122 int extra_values_to_this_arg = min(arg.max_values - arg.min_values, extra_values_to_distribute);
123 num_values_for_arg[i] += extra_values_to_this_arg;
124 extra_values_to_distribute -= extra_values_to_this_arg;
125 if (extra_values_to_distribute == 0)
126 break;
127 }
128
129 if (extra_values_to_distribute > 0) {
130 // We still have too many values :(
131 print_usage_and_exit();
132 }
133
134 for (size_t i = 0; i < m_positional_args.size(); i++) {
135 auto& arg = m_positional_args[i];
136 for (int j = 0; j < num_values_for_arg[i]; j++) {
137 const char* value = argv[optind++];
138 if (!arg.accept_value(value)) {
139 fprintf(stderr, "Invalid value for argument %s\n", arg.name);
140 print_usage_and_exit();
141 }
142 }
143 }
144
145 // We're done parsing! :)
146 // Now let's show help if requested.
147 if (m_show_help) {
148 print_usage(stdout, argv[0]);
149 exit(0);
150 }
151}
152
153void ArgsParser::print_usage(FILE* file, const char* argv0)
154{
155 fprintf(file, "Usage:\n\t%s", argv0);
156
157 for (auto& opt : m_options) {
158 if (opt.long_name && !strcmp(opt.long_name, "help"))
159 continue;
160 if (opt.requires_argument)
161 fprintf(file, " [%s %s]", opt.name_for_display().characters(), opt.value_name);
162 else
163 fprintf(file, " [%s]", opt.name_for_display().characters());
164 }
165 for (auto& arg : m_positional_args) {
166 bool required = arg.min_values > 0;
167 bool repeated = arg.max_values > 1;
168
169 if (required && repeated)
170 fprintf(file, " <%s...>", arg.name);
171 else if (required && !repeated)
172 fprintf(file, " <%s>", arg.name);
173 else if (!required && repeated)
174 fprintf(file, " [%s...]", arg.name);
175 else if (!required && !repeated)
176 fprintf(file, " [%s]", arg.name);
177 }
178
179 if (!m_options.is_empty())
180 fprintf(file, "\nOptions:\n");
181
182 for (auto& opt : m_options) {
183 auto print_argument = [&]() {
184 if (opt.value_name) {
185 if (opt.requires_argument)
186 fprintf(file, " %s", opt.value_name);
187 else
188 fprintf(file, " [%s]", opt.value_name);
189 }
190 };
191 fprintf(file, "\t");
192 if (opt.short_name) {
193 fprintf(file, "-%c", opt.short_name);
194 print_argument();
195 }
196 if (opt.short_name && opt.long_name)
197 fprintf(file, ", ");
198 if (opt.long_name) {
199 fprintf(file, "--%s", opt.long_name);
200 print_argument();
201 }
202
203 if (opt.help_string)
204 fprintf(file, "\t%s", opt.help_string);
205 fprintf(file, "\n");
206 }
207
208 if (!m_positional_args.is_empty())
209 fprintf(file, "\nArguments:\n");
210
211 for (auto& arg : m_positional_args) {
212 fprintf(file, "\t%s", arg.name);
213 if (arg.help_string)
214 fprintf(file, "\t%s", arg.help_string);
215 fprintf(file, "\n");
216 }
217}
218
219void ArgsParser::add_option(Option&& option)
220{
221 m_options.append(move(option));
222}
223
224void ArgsParser::add_option(bool& value, const char* help_string, const char* long_name, char short_name)
225{
226 Option option {
227 false,
228 help_string,
229 long_name,
230 short_name,
231 nullptr,
232 [&value](const char* s) {
233 ASSERT(s == nullptr);
234 value = true;
235 return true;
236 }
237 };
238 add_option(move(option));
239}
240
241void ArgsParser::add_option(const char*& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
242{
243 Option option {
244 true,
245 help_string,
246 long_name,
247 short_name,
248 value_name,
249 [&value](const char* s) {
250 value = s;
251 return true;
252 }
253 };
254 add_option(move(option));
255}
256
257void ArgsParser::add_option(int& value, const char* help_string, const char* long_name, char short_name, const char* value_name)
258{
259 Option option {
260 true,
261 help_string,
262 long_name,
263 short_name,
264 value_name,
265 [&value](const char* s) {
266 bool ok;
267 value = StringView(s).to_int(ok);
268 return ok;
269 }
270 };
271 add_option(move(option));
272}
273
274void ArgsParser::add_positional_argument(Arg&& arg)
275{
276 m_positional_args.append(move(arg));
277}
278
279void ArgsParser::add_positional_argument(const char*& value, const char* help_string, const char* name, Required required)
280{
281 Arg arg {
282 help_string,
283 name,
284 required == Required::Yes ? 1 : 0,
285 1,
286 [&value](const char* s) {
287 value = s;
288 return true;
289 }
290 };
291 add_positional_argument(move(arg));
292}
293
294void ArgsParser::add_positional_argument(int& value, const char* help_string, const char* name, Required required)
295{
296 Arg arg {
297 help_string,
298 name,
299 required == Required::Yes ? 1 : 0,
300 1,
301 [&value](const char* s) {
302 bool ok;
303 value = StringView(s).to_int(ok);
304 return ok;
305 }
306 };
307 add_positional_argument(move(arg));
308}
309
310void ArgsParser::add_positional_argument(Vector<const char*>& values, const char* help_string, const char* name, Required required)
311{
312 Arg arg {
313 help_string,
314 name,
315 required == Required::Yes ? 1 : 0,
316 INT_MAX,
317 [&values](const char* s) {
318 values.append(s);
319 return true;
320 }
321 };
322 add_positional_argument(move(arg));
323}
324
325}