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->argc(), qApp1->argv(), 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->argc(), qApp1->argv(), 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 
72 bool OptionParser::parse( bool untilFirstSwitchOnly ) {
73  // qDebug( "parse(%s)", args.join( QString( "," ) ).ascii() );
74  // push all arguments as we got them on a stack
75  // more pushes might following when parsing condensed arguments
76  // like --key=value.
77  QStack<QString> stack;
78  {
79  QStringListIterator it(args);
80  it.toBack();
81  while( it.hasPrevious() ) {
82  stack.push( it.previous() );
83  }
84  }
85 
86  const OptionConstIterator obegin = options.begin();
87  const OptionConstIterator oend = options.end();
88  enum { StartState, ExpectingState, OptionalState } state = StartState;
89  Option currOpt;
90  enum TokenType { LongOpt, ShortOpt, Arg, End } t, currType = End;
91  bool extraLoop = true; // we'll do an extra round. fake an End argument
92  while ( !stack.isEmpty() || extraLoop ) {
93  QString a;
94  QString origA;
95  // identify argument type
96  if ( !stack.isEmpty() ) {
97  a = stack.pop();
98  currArg++;
99  origA = a;
100  // qDebug( "popped %s", a.ascii() );
101  if ( a.startsWith( QString::fromLatin1( "--" ) ) ) {
102  // recognized long option
103  a = a.mid( 2 );
104  if ( a.isEmpty() ) {
105  qWarning( "'--' feature not supported, yet" );
106  exit( 2 );
107  }
108  t = LongOpt;
109  // split key=value style arguments
110  int equal = a.indexOf( '=' );
111  if ( equal >= 0 ) {
112  stack.push( a.mid( equal + 1 ) );
113  currArg--;
114  a = a.left( equal );
115  }
116  } else if ( a.length() == 1 ) {
117  t = Arg;
118  } else if ( a[0] == '-' ) {
119 #if 0 // compat mode for -long style options
120  if ( a.length() == 2 ) {
121  t = ShortOpt;
122  a = a[1];
123  } else {
124  a = a.mid( 1 );
125  t = LongOpt;
126  // split key=value style arguments
127  int equal = a.find( '=' );
128  if ( equal >= 0 ) {
129  stack.push( a.mid( equal + 1 ) );
130  currArg--;
131  a = a.left( equal );
132  }
133  }
134 #else
135  // short option
136  t = ShortOpt;
137  // followed by an argument ? push it for later processing.
138  if ( a.length() > 2 ) {
139  stack.push( a.mid( 2 ) );
140  currArg--;
141  }
142  a = a[1];
143 #endif
144  } else {
145  t = Arg;
146  }
147  } else {
148  // faked closing argument
149  t = End;
150  }
151  // look up among known list of options
152  Option opt;
153  if ( t != End ) {
154  OptionConstIterator oit = obegin;
155  while ( oit != oend ) {
156  const Option &o = *oit;
157  if ( ( t == LongOpt && a == o.lname ) || // ### check state
158  ( t == ShortOpt && a[0].unicode() == o.sname ) ) {
159  opt = o;
160  break;
161  }
162  ++oit;
163  }
164  if ( t == LongOpt && opt.type == OUnknown ) {
165  if ( currOpt.type != OVarLen ) {
166  qWarning( "Unknown option --%s", a.toAscii().data() );
167  return false;
168  } else {
169  // VarLength options support arguments starting with '-'
170  t = Arg;
171  }
172  } else if ( t == ShortOpt && opt.type == OUnknown ) {
173  if ( currOpt.type != OVarLen ) {
174  qWarning( "Unknown option -%c", a[0].unicode() );
175  return false;
176  } else {
177  // VarLength options support arguments starting with '-'
178  t = Arg;
179  }
180  }
181  } else {
182  opt = Option( OEnd );
183  }
184 
185  // interpret result
186  switch ( state ) {
187  case StartState:
188  if ( opt.type == OSwitch ) {
189  setSwitch( opt );
190  setOptions.insert( opt.lname, 1 );
191  setOptions.insert( QString( QChar( opt.sname ) ), 1 );
192  } else if ( opt.type == OArg1 || opt.type == ORepeat ) {
193  state = ExpectingState;
194  currOpt = opt;
195  currType = t;
196  setOptions.insert( opt.lname, 1 );
197  setOptions.insert( QString( QChar( opt.sname ) ), 1 );
198  } else if ( opt.type == OOpt || opt.type == OVarLen ) {
199  state = OptionalState;
200  currOpt = opt;
201  currType = t;
202  setOptions.insert( opt.lname, 1 );
203  setOptions.insert( QString( QChar( opt.sname ) ), 1 );
204  } else if ( opt.type == OEnd ) {
205  // we're done
206  } else if ( opt.type == OUnknown && t == Arg ) {
207  if ( numReqArgs > 0 ) {
208  if ( reqArg.stringValue->isNull() ) { // ###
209  *reqArg.stringValue = a;
210  } else {
211  qWarning( "Too many arguments" );
212  return false;
213  }
214  } else if ( numOptArgs > 0 ) {
215  if ( optArg.stringValue->isNull() ) { // ###
216  *optArg.stringValue = a;
217  } else {
218  qWarning( "Too many arguments" );
219  return false;
220  }
221  }
222  } else {
223  qFatal( "unhandled StartState case %d", opt.type );
224  }
225  break; //--- fino a qui ad indentare
226  case ExpectingState:
227  if ( t == Arg ) {
228  if ( currOpt.type == OArg1 ) {
229  *currOpt.stringValue = a;
230  state = StartState;
231  } else if ( currOpt.type == ORepeat ) {
232  currOpt.listValue->append( a );
233  state = StartState;
234  } else {
235  abort();
236  }
237  } else {
238  QString n = currType == LongOpt ?
239  currOpt.lname : QString( QChar( currOpt.sname ) );
240  qWarning( "Expected an argument after '%s' option", n.toAscii().data() );
241  return false;
242  }
243  break;
244  case OptionalState:
245  if ( t == Arg ) {
246  if ( currOpt.type == OOpt ) {
247  *currOpt.stringValue = a;
248  state = StartState;
249  } else if ( currOpt.type == OVarLen ) {
250  currOpt.listValue->append( origA );
251  // remain in this state
252  } else {
253  abort();
254  }
255  } else {
256  // optional argument not specified
257  if ( currOpt.type == OOpt )
258  *currOpt.stringValue = currOpt.def;
259  if ( t != End ) {
260  // re-evaluate current argument
261  stack.push( origA );
262  currArg--;
263  }
264  state = StartState;
265  }
266  break;
267  }
268 
269  if ( untilFirstSwitchOnly && opt.type == OSwitch )
270  return true;
271 
272  // are we in the extra loop ? if so, flag the final end
273  if ( t == End )
274  extraLoop = false;
275  }
276 
277  if ( numReqArgs > 0 && reqArg.stringValue->isNull() ) {
278  qWarning( "Lacking required argument" );
279  return false;
280  }
281 
282  return true;
283 }
284 
285 void OptionParser::addOption( Option o ) {
286  // ### check for conflicts
287  options.append( o );
288 }
289 
290 void OptionParser::addSwitch( const QString &lname, bool *b ) {
291  Option opt( OSwitch, 0, lname );
292  opt.boolValue = b;
293  addOption( opt );
294  // ### could do all inits at the beginning of parse()
295  *b = false;
296 }
297 
298 void OptionParser::setSwitch( const Option &o ) {
299  assert( o.type == OSwitch );
300  *o.boolValue = true;
301 }
302 
303 void OptionParser::addOption( char s, const QString &l, QString *v ) {
304  Option opt( OArg1, s, l );
305  opt.stringValue = v;
306  addOption( opt );
307  *v = QString::null;
308 }
309 
310 void OptionParser::addVarLengthOption( const QString &l, QStringList *v ) {
311  Option opt( OVarLen, 0, l );
312  opt.listValue = v;
313  addOption( opt );
314  *v = QStringList();
315 }
316 
317 void OptionParser::addRepeatableOption( char s, QStringList *v ) {
318  Option opt( ORepeat, s, QString::null );
319  opt.listValue = v;
320  addOption( opt );
321  *v = QStringList();
322 }
323 
324 void OptionParser::addRepeatableOption( const QString &l, QStringList *v ) {
325  Option opt( ORepeat, 0, l );
326  opt.listValue = v;
327  addOption( opt );
328  *v = QStringList();
329 }
330 
331 void OptionParser::addOptionalOption( const QString &l, QString *v, const QString &def ) {
332  addOptionalOption( 0, l, v, def );
333 }
334 
335 void OptionParser::addOptionalOption( char s, const QString &l, QString *v, const QString &def ) {
336  Option opt( OOpt, s, l );
337  opt.stringValue = v;
338  opt.def = def;
339  addOption( opt );
340  *v = QString::null;
341 }
342 
343 void OptionParser::addArgument( const QString &name, QString *v ) {
344  Option opt( OUnknown, 0, name );
345  opt.stringValue = v;
346  reqArg = opt;
347  ++numReqArgs;
348  *v = QString::null;
349 }
350 
351 void OptionParser::addOptionalArgument( const QString &name, QString *v ) {
352  Option opt( OUnknown, 0, name );
353  opt.stringValue = v;
354  optArg = opt;
355  ++numOptArgs;
356  *v = QString::null;
357 }
358 
359 
360 bool OptionParser::isSet( const QString &name ) const {
361  return setOptions.find( name ) != setOptions.end();
362 }
363 
364 } // end namespace farsa
365