1 /*
2 This file is part of LilyPond, the GNU music typesetter.
3
4 Copyright (C) 1996--2020 Han-Wen Nienhuys, <hanwen@xs4all.nl>
5
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "getopt-long.hh"
21
22 #include <cstring>
23 #include <cassert>
24 #include <cstdlib>
25
26 #include "config.hh"
27 #include "international.hh"
28
29 #if !HAVE_GETTEXT
30 inline char *
gettext(char const * s)31 gettext (char const *s)
32 {
33 return (char *)s;
34 }
35 #else
36 #include <libintl.h>
37 #endif
38
39 using std::string;
40
41 long
get_argument_index()42 Getopt_long::get_argument_index ()
43 {
44 long l;
45 if (!optional_argument_str0_
46 || sscanf (optional_argument_str0_, "%ld", &l) != 1)
47 report (E_ILLEGALARG);
48
49 return l;
50 }
51
52 const Long_option_init *
parselong()53 Getopt_long::parselong ()
54 {
55 char const *optnm = arg_value_char_a_a_[array_index_] + 2;
56 assert (*optnm);
57
58 char const *endopt = strchr (optnm, '=');
59 size_t searchlen = (endopt) ? (size_t) (endopt - optnm) : strlen (optnm);
60
61 found_option_ = 0;
62 for (int i = 0; i < table_len_; i++)
63 {
64 char const *ln = option_a_[i].longname_str0_;
65
66 if (ln && !strncmp (ln, optnm, searchlen))
67 {
68 found_option_ = option_a_ + i;
69 break;
70 }
71 }
72
73 if (!found_option_)
74 {
75 report (E_UNKNOWNOPTION);
76 return 0;
77 }
78 array_index_++;
79 argument_index_ = 0;
80
81 if (found_option_->take_arg_str0_)
82 {
83 if (endopt)
84 optional_argument_str0_ = endopt + 1; // a '='
85 else
86 {
87 optional_argument_str0_ = arg_value_char_a_a_[array_index_];
88 array_index_++;
89 }
90 if (!optional_argument_str0_)
91 report (E_ARGEXPECT);
92 }
93 else
94 {
95 optional_argument_str0_ = 0;
96 if (endopt)
97 report (E_NOARGEXPECT);
98 }
99
100 return found_option_;
101 }
102
103 string
to_string() const104 Long_option_init::to_string () const
105 {
106 string str;
107 if (shortname_char_)
108 str += string ("-") + shortname_char_;
109 if (shortname_char_ && longname_str0_)
110 str += ", ";
111 if (longname_str0_)
112 str += string ("--") + longname_str0_;
113 return str;
114 }
115
116 string
str_for_help() const117 Long_option_init::str_for_help () const
118 {
119 string s;
120 if (shortname_char_)
121 {
122 s += '-';
123 s += shortname_char_;
124 }
125 else
126 s = " ";
127
128 s = s + ((shortname_char_ && longname_str0_) ? ", " : " ");
129
130 if (longname_str0_)
131 s = s + "--" + longname_str0_;
132
133 if (take_arg_str0_)
134 {
135 if (longname_str0_)
136 s = s + "=";
137 else
138 s = s + " ";
139
140 s = s + gettext (take_arg_str0_);
141 }
142 return s;
143 }
144
145 // report an error, GNU style.
146 void
report(Errorcod c)147 Getopt_long::report (Errorcod c)
148 {
149 error_ = c;
150 if (!error_out_)
151 return;
152
153 string str = arg_value_char_a_a_[0];
154 str += ": ";
155 switch (c)
156 {
157 case E_ARGEXPECT:
158 str += _f ("option `%s' requires an argument",
159 found_option_->to_string ());
160 break;
161 case E_NOARGEXPECT:
162 str += _f ("option `%s' does not allow an argument",
163 found_option_->to_string ());
164 break;
165 case E_UNKNOWNOPTION:
166 str += _f ("unrecognized option: `%s'",
167 argument_index_
168 ? ("-" + string (1, arg_value_char_a_a_[array_index_][argument_index_]))
169 : string (arg_value_char_a_a_[array_index_]));
170 break;
171 case E_ILLEGALARG:
172 str += _f ("invalid argument `%s' to option `%s'",
173 optional_argument_str0_, found_option_->to_string ());
174 break;
175 default:
176 assert (false);
177 }
178 fprintf (error_out_, "%s\n", str.c_str ());
179 exit (2);
180 }
181
182 const Long_option_init *
parseshort()183 Getopt_long::parseshort ()
184 {
185 char c = arg_value_char_a_a_[array_index_][argument_index_];
186 found_option_ = 0;
187 assert (c);
188
189 for (int i = 0; i < table_len_; i++)
190 if (option_a_[i].shortname_char_ == c)
191 {
192 found_option_ = option_a_ + i;
193 break;
194 }
195
196 if (!found_option_)
197 {
198 report (E_UNKNOWNOPTION);
199 return 0;
200 }
201
202 argument_index_++;
203 if (!found_option_->take_arg_str0_)
204 {
205 optional_argument_str0_ = 0;
206 return found_option_;
207 }
208 optional_argument_str0_ = arg_value_char_a_a_[array_index_] + argument_index_;
209
210 array_index_++;
211 argument_index_ = 0;
212
213 if (!optional_argument_str0_[0])
214 {
215 optional_argument_str0_ = arg_value_char_a_a_[array_index_];
216 array_index_++;
217 }
218 if (!optional_argument_str0_)
219 report (E_ARGEXPECT);
220
221 return found_option_;
222 }
223
224 const Long_option_init *
operator ()()225 Getopt_long::operator () ()
226 {
227 if (!ok ())
228 return 0;
229
230 next ();
231 if (!ok ())
232 return 0;
233
234 if (argument_index_)
235 return parseshort ();
236
237 char const *argument = arg_value_char_a_a_[array_index_];
238
239 if (argument[0] != '-')
240 return 0;
241
242 if (argument[1] == '-') // what to do with "command -- bla"
243 {
244 if (argument[2])
245 return parselong ();
246 else
247 return 0;
248 }
249 else
250 {
251 if (argument[ 1 ])
252 {
253 argument_index_ = 1;
254 return parseshort ();
255 }
256 else
257 return 0;
258 }
259 }
260
Getopt_long(int c,char ** v,Long_option_init * lo)261 Getopt_long::Getopt_long (int c, char **v, Long_option_init *lo)
262 {
263 option_a_ = lo;
264 error_out_ = stderr;
265 arg_value_char_a_a_ = v;
266 argument_count_ = c;
267 array_index_ = 1;
268 argument_index_ = 0;
269
270 // reached end of option table?
271 table_len_ = 0;
272 for (int i = 0; option_a_[i].longname_str0_ || option_a_[i].shortname_char_; i++)
273 table_len_++;
274 }
275
276 bool
ok() const277 Getopt_long::ok () const
278 {
279 return array_index_ < argument_count_;
280 }
281
282 void
next()283 Getopt_long::next ()
284 {
285 error_ = E_NOERROR;
286 while (array_index_ < argument_count_
287 && !arg_value_char_a_a_[array_index_][argument_index_])
288 {
289 array_index_++;
290 argument_index_ = 0;
291 }
292 }
293
294 char const *
current_arg()295 Getopt_long::current_arg ()
296 {
297 if (array_index_ >= argument_count_)
298 return 0;
299 char const *a = arg_value_char_a_a_[array_index_];
300 return a + argument_index_;
301 }
302
303 char const *
get_next_arg()304 Getopt_long::get_next_arg ()
305 {
306 char const *a = current_arg ();
307 if (a)
308 {
309 array_index_++;
310 argument_index_ = 0;
311 }
312 return a;
313 }
314
315 const int EXTRA_SPACES = 5;
316
317 string
table_string(Long_option_init * l)318 Long_option_init::table_string (Long_option_init *l)
319 {
320 string tabstr = "";
321
322 size_t wid = 0;
323 for (int i = 0; l[i].shortname_char_ || l[i].longname_str0_; i++)
324 wid = std::max (wid, l[i].str_for_help ().length ());
325
326 for (int i = 0; l[i].shortname_char_ || l[i].longname_str0_; i++)
327 {
328 string s = " " + l[i].str_for_help ();
329 s += string (wid - s.length () + EXTRA_SPACES, ' ');
330
331 string help_text (gettext (l[i].help_str0_));
332 replace_all (&help_text, "\n",
333 "\n" + string (wid + EXTRA_SPACES + 2, ' '));
334 tabstr += s + help_text + "\n";
335 }
336
337 return tabstr;
338 }
339
340 int
compare(Long_option_init const & a,Long_option_init const & b)341 Long_option_init::compare (Long_option_init const &a, Long_option_init const &b)
342 {
343 if (a.shortname_char_ && b.shortname_char_ && a.shortname_char_ - b.shortname_char_)
344 return a.shortname_char_ - b.shortname_char_;
345
346 if (b.shortname_char_ && a.longname_str0_)
347 {
348 char s[2] = {b.shortname_char_, 0};
349 return strcmp (a.longname_str0_, s);
350 }
351 if (a.shortname_char_ && b.longname_str0_)
352 {
353 char s[2] = {a.shortname_char_, 0};
354 return strcmp (s, b.longname_str0_);
355 }
356
357 return strcmp (a.longname_str0_, b.longname_str0_);
358 }
359