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