optionparser.cpp
1 /********************************************************************************
2  * FARSA - Utilities *
3  * Copyright (C) 2005-2011 Gianluca Massera <emmegian@yahoo.it> *
4  * *
5  * This program is free software; you can redistribute it and/or modify *
6  * it under the terms of the GNU General Public License as published by *
7  * the Free Software Foundation; either version 2 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License *
16  * along with this program; if not, write to the Free Software *
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
18  ********************************************************************************/
19 
20 #define QT_NO_CAST_ASCII
21 #define QT_NO_ASCII_CAST
22 
23 #include "optionparser.h"
24 
25 #include <QCoreApplication>
26 #include <QFileInfo>
27 #include <QStack>
28 #include <cstdlib>
29 #include <cassert>
30 
31 namespace farsa {
32 
34  QCoreApplication* qApp1 = QCoreApplication::instance();
35  if ( !qApp1 ) {
36  qFatal( "OptionParser: requires a QCoreApplication instance to be constructed first" );
37  }
38  init( qApp1->arguments(), 1 );
39 }
40 
41 OptionParser::OptionParser( int offset ) {
42  QCoreApplication* qApp1 = QCoreApplication::instance();
43  if ( !qApp1 ) {
44  qFatal( "OptionParser: requires a QApplication instance to be constructed first" );
45  }
46  init( qApp1->arguments(), offset );
47 }
48 
49 OptionParser::OptionParser( int argc, char *argv[] ) {
50  init( argc, argv );
51 }
52 
53 OptionParser::OptionParser( const QStringList &a )
54  : args( a ) {
55  init( 0, 0 );
56 }
57 
58 void OptionParser::init( int argc, char *argv[], int offset ) {
59  numReqArgs = numOptArgs = 0;
60  currArg = 1; // appname is not part of the arguments
61  if ( argc ) {
62  // application name
63  aname = QFileInfo( QString::fromUtf8( argv[0] ) ).fileName();
64  // arguments
65  for ( int i = offset; i < argc; ++i ) {
66  args.append( QString::fromUtf8( argv[i] ) );
67  }
68  }
69 }
70 
71 void OptionParser::init( const QStringList& arguments, int offset ) {
72  numReqArgs = numOptArgs = 0;
73  currArg = 1; // appname is not part of the arguments
74  if ( !arguments.isEmpty() ) {
75  // application name
76  aname = QFileInfo( arguments[0] ).fileName();
77  // arguments
78  for ( int i = offset; i < arguments.size(); ++i ) {
79  args.append( arguments[i] );
80  }
81  }
82 }
83 
84 
85 bool OptionParser::parse( bool untilFirstSwitchOnly ) {
86  // qDebug( "parse(%s)", args.join( QString( "," ) ).ascii() );
87  // push all arguments as we got them on a stack
88  // more pushes might following when parsing condensed arguments
89  // like --key=value.
90  QStack<QString> stack;
91  {
92  QStringListIterator it(args);
93  it.toBack();
94  while( it.hasPrevious() ) {
95  stack.push( it.previous() );
96  }
97  }
98 
99  const OptionConstIterator obegin = options.begin();
100  const OptionConstIterator oend = options.end();
101  enum { StartState, ExpectingState, OptionalState } state = StartState;
102  Option currOpt;
103  enum TokenType { LongOpt, ShortOpt, Arg, End } t, currType = End;
104  bool extraLoop = true; // we'll do an extra round. fake an End argument
105  while ( !stack.isEmpty() || extraLoop ) {
106  QString a;
107  QString origA;
108  // identify argument type
109  if ( !stack.isEmpty() ) {
110  a = stack.pop();
111  currArg++;
112  origA = a;
113  // qDebug( "popped %s", a.ascii() );
114  if ( a.startsWith( QString::fromLatin1( "--" ) ) ) {
115  // recognized long option
116  a = a.mid( 2 );
117  if ( a.isEmpty() ) {
118  qWarning( "'--' feature not supported, yet" );
119  exit( 2 );
120  }
121  t = LongOpt;
122  // split key=value style arguments
123  int equal = a.indexOf( '=' );
124  if ( equal >= 0 ) {
125  stack.push( a.mid( equal + 1 ) );
126  currArg--;
127  a = a.left( equal );
128  }
129  } else if ( a.length() == 1 ) {
130  t = Arg;
131  } else if ( a[0] == '-' ) {
132 #if 0 // compat mode for -long style options
133  if ( a.length() == 2 ) {
134  t = ShortOpt;
135  a = a[1];
136  } else {
137  a = a.mid( 1 );
138  t = LongOpt;
139  // split key=value style arguments
140  int equal = a.find( '=' );
141  if ( equal >= 0 ) {
142  stack.push( a.mid( equal + 1 ) );
143  currArg--;
144  a = a.left( equal );
145  }
146  }
147 #else
148  // short option
149  t = ShortOpt;
150  // followed by an argument ? push it for later processing.
151  if ( a.length() > 2 ) {
152  stack.push( a.mid( 2 ) );
153  currArg--;
154  }
155  a = a[1];
156 #endif
157  } else {
158  t = Arg;
159  }
160  } else {
161  // faked closing argument
162  t = End;
163  }
164  // look up among known list of options
165  Option opt;
166  if ( t != End ) {
167  OptionConstIterator oit = obegin;
168  while ( oit != oend ) {
169  const Option &o = *oit;
170  if ( ( t == LongOpt && a == o.lname ) || // ### check state
171  ( t == ShortOpt && a[0].unicode() == o.sname ) ) {
172  opt = o;
173  break;
174  }
175  ++oit;
176  }
177  if ( t == LongOpt && opt.type == OUnknown ) {
178  if ( currOpt.type != OVarLen ) {
179  qWarning( "Unknown option --%s", a.toLatin1().data() );
180  return false;
181  } else {
182  // VarLength options support arguments starting with '-'
183  t = Arg;
184  }
185  } else if ( t == ShortOpt && opt.type == OUnknown ) {
186  if ( currOpt.type != OVarLen ) {
187  qWarning( "Unknown option -%c", a[0].unicode() );
188  return false;
189  } else {
190  // VarLength options support arguments starting with '-'
191  t = Arg;
192  }
193  }
194  } else {
195  opt = Option( OEnd );
196  }
197 
198  // interpret result
199  switch ( state ) {
200  case StartState:
201  if ( opt.type == OSwitch ) {
202  setSwitch( opt );
203  setOptions.insert( opt.lname, 1 );
204  setOptions.insert( QString( QChar( opt.sname ) ), 1 );
205  } else if ( opt.type == OArg1 || opt.type == ORepeat ) {
206  state = ExpectingState;
207  currOpt = opt;
208  currType = t;
209  setOptions.insert( opt.lname, 1 );
210  setOptions.insert( QString( QChar( opt.sname ) ), 1 );
211  } else if ( opt.type == OOpt || opt.type == OVarLen ) {
212  state = OptionalState;
213  currOpt = opt;
214  currType = t;
215  setOptions.insert( opt.lname, 1 );
216  setOptions.insert( QString( QChar( opt.sname ) ), 1 );
217  } else if ( opt.type == OEnd ) {
218  // we're done
219  } else if ( opt.type == OUnknown && t == Arg ) {
220  if ( numReqArgs > 0 ) {
221  if ( reqArg.stringValue->isNull() ) { // ###
222  *reqArg.stringValue = a;
223  } else {
224  qWarning( "Too many arguments" );
225  return false;
226  }
227  } else if ( numOptArgs > 0 ) {
228  if ( optArg.stringValue->isNull() ) { // ###
229  *optArg.stringValue = a;
230  } else {
231  qWarning( "Too many arguments" );
232  return false;
233  }
234  }
235  } else {
236  qFatal( "unhandled StartState case %d", opt.type );
237  }
238  break; //--- fino a qui ad indentare
239  case ExpectingState:
240  if ( t == Arg ) {
241  if ( currOpt.type == OArg1 ) {
242  *currOpt.stringValue = a;
243  state = StartState;
244  } else if ( currOpt.type == ORepeat ) {
245  currOpt.listValue->append( a );
246  state = StartState;
247  } else {
248  abort();
249  }
250  } else {
251  QString n = currType == LongOpt ?
252  currOpt.lname : QString( QChar( currOpt.sname ) );
253  qWarning( "Expected an argument after '%s' option", n.toLatin1().data() );
254  return false;
255  }
256  break;
257  case OptionalState:
258  if ( t == Arg ) {
259  if ( currOpt.type == OOpt ) {
260  *currOpt.stringValue = a;
261  state = StartState;
262  } else if ( currOpt.type == OVarLen ) {
263  currOpt.listValue->append( origA );
264  // remain in this state
265  } else {
266  abort();
267  }
268  } else {
269  // optional argument not specified
270  if ( currOpt.type == OOpt )
271  *currOpt.stringValue = currOpt.def;
272  if ( t != End ) {
273  // re-evaluate current argument
274  stack.push( origA );
275  currArg--;
276  }
277  state = StartState;
278  }
279  break;
280  }
281 
282  if ( untilFirstSwitchOnly && opt.type == OSwitch )
283  return true;
284 
285  // are we in the extra loop ? if so, flag the final end
286  if ( t == End )
287  extraLoop = false;
288  }
289 
290  if ( numReqArgs > 0 && reqArg.stringValue->isNull() ) {
291  qWarning( "Lacking required argument" );
292  return false;
293  }
294 
295  return true;
296 }
297 
298 void OptionParser::addOption( Option o ) {
299  // ### check for conflicts
300  options.append( o );
301 }
302 
303 void OptionParser::addSwitch( const QString &lname, bool *b ) {
304  Option opt( OSwitch, 0, lname );
305  opt.boolValue = b;
306  addOption( opt );
307  // ### could do all inits at the beginning of parse()
308  *b = false;
309 }
310 
311 void OptionParser::setSwitch( const Option &o ) {
312  assert( o.type == OSwitch );
313  *o.boolValue = true;
314 }
315 
316 void OptionParser::addOption( char s, const QString &l, QString *v ) {
317  Option opt( OArg1, s, l );
318  opt.stringValue = v;
319  addOption( opt );
320  *v = QString::null;
321 }
322 
323 void OptionParser::addVarLengthOption( const QString &l, QStringList *v ) {
324  Option opt( OVarLen, 0, l );
325  opt.listValue = v;
326  addOption( opt );
327  *v = QStringList();
328 }
329 
330 void OptionParser::addRepeatableOption( char s, QStringList *v ) {
331  Option opt( ORepeat, s, QString::null );
332  opt.listValue = v;
333  addOption( opt );
334  *v = QStringList();
335 }
336 
337 void OptionParser::addRepeatableOption( const QString &l, QStringList *v ) {
338  Option opt( ORepeat, 0, l );
339  opt.listValue = v;
340  addOption( opt );
341  *v = QStringList();
342 }
343 
344 void OptionParser::addOptionalOption( const QString &l, QString *v, const QString &def ) {
345  addOptionalOption( 0, l, v, def );
346 }
347 
348 void OptionParser::addOptionalOption( char s, const QString &l, QString *v, const QString &def ) {
349  Option opt( OOpt, s, l );
350  opt.stringValue = v;
351  opt.def = def;
352  addOption( opt );
353  *v = QString::null;
354 }
355 
356 void OptionParser::addArgument( const QString &name, QString *v ) {
357  Option opt( OUnknown, 0, name );
358  opt.stringValue = v;
359  reqArg = opt;
360  ++numReqArgs;
361  *v = QString::null;
362 }
363 
364 void OptionParser::addOptionalArgument( const QString &name, QString *v ) {
365  Option opt( OUnknown, 0, name );
366  opt.stringValue = v;
367  optArg = opt;
368  ++numOptArgs;
369  *v = QString::null;
370 }
371 
372 
373 bool OptionParser::isSet( const QString &name ) const {
374  return setOptions.find( name ) != setOptions.end();
375 }
376 
377 } // end namespace farsa
378 
void addRepeatableOption(char s, QStringList *v)
A macro to deprecate functions.
void addOption(char s, const QString &l, QString *v)
void addSwitch(const QString &lname, bool *b)
void addOptionalOption(const QString &l, QString *v, const QString &def)
OptionParser()
Constructs a command line parser from the arguments stored in a previously created QApplication insta...
void addOptionalArgument(const QString &name, QString *v)
void addArgument(const QString &name, QString *v)
void addVarLengthOption(const QString &l, QStringList *v)
bool isSet(const QString &name) const