1 /****************************************************************************
2 * MeshLab                                                           o o     *
3 * An extendible mesh processor                                    o     o   *
4 *                                                                _   O  _   *
5 * Copyright(C) 2005, 2009                                          \/)\/    *
6 * Visual Computing Lab                                            /\/|      *
7 * ISTI - Italian National Research Council                           |      *
8 *                                                                    \      *
9 * All rights reserved.                                                      *
10 *                                                                           *
11 * This program is free software; you can redistribute it and/or modify      *
12 * it under the terms of the GNU General Public License as published by      *
13 * the Free Software Foundation; either version 2 of the License, or         *
14 * (at your option) any later version.                                       *
15 *                                                                           *
16 * This program is distributed in the hope that it will be useful,           *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
19 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)          *
20 * for more details.                                                         *
21 *                                                                           *
22 ****************************************************************************/
23 
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <QtCore/QVariant>
27 #include "qgetopt.h"
28 
29 #include <iostream>
30 using namespace std;
31 
GetOpt(int argc,char * argv[])32 GetOpt::GetOpt(int argc, char *argv[] ) {
33   appname = argv[0];
34   for(int i = 1; i < argc; i++)
35     args.push_back(argv[i]);
36 }
37 
GetOpt(const QStringList & a)38 GetOpt::GetOpt(const QStringList &a): args(a) {
39   appname = a[0];
40   args = a;
41   args.pop_front();
42 }
43 
44  //add an option without a value
addSwitch(char s,const QString & name,const QString & description,bool * b)45 void GetOpt::addSwitch(char s, const QString &name, const QString &description, bool *b ) {
46   Option option;
47   assert(!findOption(s, option));
48   assert(!findArg(name, option));
49   option.type = Option::SWITCH;
50   option.o = s;
51   option.name = name;
52   option.description = description;
53   option.boolean_value = b;
54   options.push_back(option);
55 }
56 
57   //add a valued option (v will be left untouched if the option is not given)
addOption(char s,const QString & name,const QString & description,QVariant * v)58 void GetOpt::addOption(char s, const QString &name, const QString &description, QVariant *v ) {
59   Option option(Option::OPTION, s, name, description);
60   option.value = v;
61 
62   assert(!findOption(s, option)); //TODO make this check systematic
63   assert(!findArg(name, option));
64 
65   options.push_back(option);
66 
67 
68 }
69   //add an argument
addArgument(const QString & name,const QString & description,QVariant * v)70 void GetOpt::addArgument(const QString &name, const QString &description, QVariant *v) {
71   Option option;
72   option.value = v;
73   addArgument(name, description, option);
74 }
75 
addArgument(const QString & name,const QString & description,QString * v)76 void GetOpt::addArgument(const QString &name, const QString &description, QString *v) {
77   Option option;
78   option.string_value = v;
79   addArgument(name, description, option);
80 }
81 
addArgument(const QString & name,const QString & description,float * v)82 void GetOpt::addArgument(const QString &name, const QString &description, float *v) {
83   Option option;
84   option.float_value = v;
85   addArgument(name, description, option);
86 }
87 
addArgument(const QString & name,const QString & description,double * v)88 void GetOpt::addArgument(const QString &name, const QString &description, double *v) {
89   Option option;
90   option.double_value = v;
91   addArgument(name, description, option);
92 }
93 
addArgument(const QString & name,const QString & description,int * v)94 void GetOpt::addArgument(const QString &name, const QString &description, int *v) {
95   Option option;
96   option.int_value = v;
97   addArgument(name, description, option);
98 }
99 
addArgument(const QString & name,const QString & description,bool * v)100 void GetOpt::addArgument(const QString &name, const QString &description, bool *v) {
101   Option option;
102   option.boolean_value = v;
103   addArgument(name, description, option);
104 }
105 
addArgument(const QString & name,const QString & description,Option option)106 void GetOpt::addArgument(const QString &name, const QString &description, Option option) {
107     assert(!findArg(name, option));
108     option.type = Option::ARGUMENT;
109     option.name = name;
110     option.description = description;
111     options.push_back(option);
112 }
113 
addOption(char s,const QString & longname,const QString & description,QString * v)114 void GetOpt::addOption(char s, const QString &longname, const QString &description, QString *v) {
115     Option option(Option::OPTION, s, longname, description);
116     option.string_value = v;
117     options.push_back(option);
118 }
addOption(char s,const QString & longname,const QString & description,float * v)119 void GetOpt::addOption(char s, const QString &longname, const QString &description, float *v) {
120 	Option option(Option::OPTION, s, longname, description);
121 	option.float_value = v;
122 	options.push_back(option);
123 }
addOption(char s,const QString & longname,const QString & description,double * v)124 void GetOpt::addOption(char s, const QString &longname, const QString &description, double *v) {
125     Option option(Option::OPTION, s, longname, description);
126     option.double_value = v;
127     options.push_back(option);
128 }
addOption(char s,const QString & longname,const QString & description,int * v)129 void GetOpt::addOption(char s, const QString &longname, const QString &description, int *v) {
130     Option option(Option::OPTION, s, longname, description);
131     option.int_value = v;
132     options.push_back(option);
133 }
addOption(char s,const QString & longname,const QString & description,bool * v)134 void GetOpt::addOption(char s, const QString &longname, const QString &description, bool *v) {
135     Option option(Option::OPTION, s, longname, description);
136     option.boolean_value = v;
137     options.push_back(option);
138 }
139 
140   //add an optional agrument
addOptionalArgument(const QString & name,const QString & description,QVariant * v)141 void GetOpt::addOptionalArgument(const QString &name, const QString &description, QVariant *v) {
142   Option option;
143   assert(!findArg(name, option));
144   option.type = Option::OPTIONAL;
145   option.name = name;
146   option.description = description;
147   option.value = v;
148   options.push_back(option);
149 }
150   //return application name
applicationName()151 QString &GetOpt::applicationName() {
152   return appname;
153 }
154 
155   //return usage string
usage()156 QString GetOpt::usage() {
157   QString u = "Usage: " + appname;
158   //arguments
159   bool has_optionals = false;
160   bool has_options = false;
161   for(int i = 0; i < options.size(); i++) {
162     if(options[i].type == Option::OPTION) has_options = true;
163     if(options[i].type == Option::OPTIONAL) has_optionals = true;
164     if(options[i].type != Option::ARGUMENT) continue;
165     u += " <" + options[i].name + ">";
166   }
167   //optional arguments
168   if(has_optionals) {
169     u += " [";
170     for(int i = 0; i < options.size(); i++) {
171       if(options[i].type != Option::OPTIONAL) continue;
172       u += "<" + options[i].name + ">";
173     }
174     u += "]";
175   }
176 
177   if(unlimitedArgs) {
178 	u += " [ARGS]";
179   }
180 
181   if(has_options) {
182     u += " [-";
183     for(int i = 0; i < options.size(); i++) {
184       if(options[i].type != Option::OPTION) continue;
185       u += options[i].o;
186     }
187     u += "]";
188   }
189   u += "\n\n";
190   u += help;
191   u += "\n\n";
192   //compute maxlen:
193   int maxlen = 0;
194   for(int i = 0; i < options.size(); i++) {
195     Option &o = options[i];
196     int len = o.name.size() + 2;
197     switch(o.type) {
198       case Option::ARGUMENT:
199       case Option::OPTIONAL: break;
200       case Option::SWITCH:   len += 5; break;
201       case Option::OPTION:   len += 16; break;
202       default: break;
203     }
204     if(len > maxlen) maxlen = len;
205   }
206   //print options and arguments in the given order
207   for(int i = 0; i < options.size(); i++) {
208     Option &o = options[i];
209     QString line = "";
210     switch(o.type) {
211       case Option::ARGUMENT:
212       case Option::OPTIONAL: line += o.name; break;
213       case Option::SWITCH:   line += "-" + QString(o.o) + " --" + o.name; break;
214       case Option::OPTION:   line += "-" + QString(o.o) + " <val> --" + o.name + " <val>"; break;
215       default: break;
216     }
217     QString blank = "";
218     blank.resize(maxlen - line.size());
219     blank.fill(' ');
220     line += blank + formatDesc(o.description, maxlen) + "\n";
221     u += line;
222   }
223   return u;
224 }
225 
parse()226 void GetOpt::parse() {
227   QString error;
228   if(!parse(error)) {
229     cerr << qPrintable(error) << endl << endl << qPrintable(usage()) << endl << endl;
230     exit(0);
231   }
232 }
233 
assignOption(Option & o,QString arg,QString & error)234 bool GetOpt::assignOption(Option &o, QString arg, QString &error) {
235     QVariant::Type type;
236     if(o.value) type = o.value->type();
237     if(o.string_value) type = QVariant::String;
238 	if(o.float_value) type = QVariant::Double;
239     if(o.double_value) type = QVariant::Double;
240     if(o.int_value) type = QVariant::Int;
241     if(o.boolean_value) type = QVariant::Bool;
242     QVariant v(arg);
243 
244     if(!v.canConvert(type) || !v.convert(type)) {
245       error = "Error while parsing option " + o.name + ": cannot convert " +
246               arg + " to: " + v.typeName();
247       return false;
248     }
249     if(o.value)         *(o.value)         = v;
250     if(o.string_value)  *(o.string_value)  = v.toString();
251 	if(o.float_value)   *(o.float_value)   = v.toFloat();
252 	if(o.double_value)  *(o.double_value)  = v.toDouble();
253     if(o.int_value)     *(o.int_value)     = v.toInt();
254     if(o.boolean_value) *(o.boolean_value) = v.toBool();
255     return true;
256 }
257 
parse(QString & error)258 bool GetOpt::parse(QString &error) {
259   for(int i = 0; i < args.size(); i++) {
260     QString arg = args[i];
261     if(args[i] == "-h" || args[i] == "--help") {
262 	  cout << qPrintable(usage()) << endl;
263       exit(0);
264     }
265     //long option
266     if(arg.startsWith( QString::fromLatin1( "--" ) ) ) {
267       arg = arg.mid( 2 );
268       if(arg.isEmpty()) {
269         error = "'--' feature not supported, yet";
270         return false;
271       }
272       Option o;
273       if(!findArg(arg, o)) {
274          error = "Unknown option: '" + arg + "'";
275         return false;
276       }
277       if(o.type == Option::SWITCH) {
278         *(o.boolean_value) = true;
279       } else { //OPTION
280         i++;
281         if(args.size() <= i) {
282           error =  "Missing value for option: " + arg;
283           return false;
284         }
285         arg = args[i];
286         if(i == args.size() || arg[0] == '-') {
287           error = "Missing argument after option '" + arg + "'";
288           return false;
289         }
290         if(!assignOption(o, arg, error))
291             return false;
292       }
293 
294     //option
295     } else if( arg[0] == '-' ) {
296       if(arg.size() != 2) {
297         error = "Invalid option: " + arg;
298         return false;
299       }
300       Option o;
301       if(!findOption(arg[1].toLatin1(), o)) {
302          error = "Unknown option: '" + arg + "'";
303         return false;
304       }
305       if(o.type == Option::SWITCH) {
306         *(o.boolean_value) = true;
307       } else { //OPTION
308         i++;
309         if(args.size() <= i) {
310           error =  "Missing value for option: " + arg;
311           return false;
312         }
313         arg = args[i];
314         if(i == args.size()) {
315           error = "Missing argument after option '" + arg + "'";
316           return false;
317         }
318         if(!assignOption(o, arg, error))
319             return false;
320 /*
321         QVariant v(arg);
322         if(!v.canConvert(o.value->type()) || !v.convert(o.value->type())) {
323           error = "Error while parsing option " + o.name + ": cannot convert " +
324                   arg + " to: " + o.value->typeName();
325           return false;
326         }
327         *(o.value) = v; */
328       }
329     //argument
330     } else {
331       arguments.push_back(arg);
332     }
333   }
334 
335   //regular arguments
336   for(int i = 0; i < options.size(); i++) {
337     Option &o = options[i];
338     if(o.type != Option::ARGUMENT) continue;
339     if(arguments.isEmpty()) {
340       error = "Too few arguments, could not parse argument '" + o.name + "'";
341       return false;
342     }
343     if(!assignOption(o, arguments.front(), error))
344         return false;
345     arguments.pop_front();
346   }
347    //optional arguments
348   for(int i = 0; i < options.size(); i++) {
349     Option &o = options[i];
350     if(o.type != Option::OPTIONAL) continue;
351     if(arguments.isEmpty()) break;
352     if(!assignOption(o, arguments.front(), error))
353         return false;
354     arguments.pop_front();
355   }
356   if(!arguments.isEmpty() && !unlimitedArgs) {
357     error = "Too many arguments";
358     return false;
359   }
360   return true;
361 }
362 
findOption(char c,Option & option)363 bool GetOpt::findOption(char c, Option &option) {
364   for(int i = 0; i < options.size(); i++) {
365     Option &o = options[i];
366     if(o.type != Option::OPTION && o.type != Option::SWITCH) continue;
367     if(o.o == c) {
368       option = o;
369       return true;
370     }
371   }
372   return false;
373 }
374 
findArg(const QString & name,Option & option)375 bool GetOpt::findArg(const QString &name, Option &option) {
376   for(int i = 0; i < options.size(); i++) {
377     Option &o = options[i];
378     if(o.name == name) {
379       option = o;
380       return true;
381     }
382   }
383   return false;
384 }
385 
formatDesc(QString desc,int len)386 QString GetOpt::formatDesc(QString desc, int len) {
387   QString output;
388   //search for last space before 79 - len characters
389   while(!desc.isEmpty()) {
390     int pos = desc.lastIndexOf(" ", 79 - len);
391     if(pos == -1) {
392       output += desc;
393       break;
394     }
395     output += desc.left(pos) + "\n";
396     QString blank;
397     blank.resize(len);
398     blank.fill(' ');
399     output += blank;
400     desc = desc.mid(pos+1);
401   }
402   return output;
403 }
404