utilities/src/optionparser.cpp

00001 /********************************************************************************
00002  *  FARSA - Utilities                                                           *
00003  *  Copyright (C) 2005-2011 Gianluca Massera <emmegian@yahoo.it>                *
00004  *                                                                              *
00005  *  This program is free software; you can redistribute it and/or modify        *
00006  *  it under the terms of the GNU General Public License as published by        *
00007  *  the Free Software Foundation; either version 2 of the License, or           *
00008  *  (at your option) any later version.                                         *
00009  *                                                                              *
00010  *  This program is distributed in the hope that it will be useful,             *
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of              *
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               *
00013  *  GNU General Public License for more details.                                *
00014  *                                                                              *
00015  *  You should have received a copy of the GNU General Public License           *
00016  *  along with this program; if not, write to the Free Software                 *
00017  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  *
00018  ********************************************************************************/
00019 
00020 #define QT_NO_CAST_ASCII
00021 #define QT_NO_ASCII_CAST
00022 
00023 #include "optionparser.h"
00024 
00025 #include <QCoreApplication>
00026 #include <QFileInfo>
00027 #include <QStack>
00028 #include <cstdlib>
00029 #include <cassert>
00030 
00031 namespace farsa {
00032 
00033 OptionParser::OptionParser() {
00034     QCoreApplication* qApp1 = QCoreApplication::instance();
00035     if ( !qApp1 ) {
00036        qFatal( "OptionParser: requires a QCoreApplication instance to be constructed first" );
00037     }
00038     init( qApp1->argc(), qApp1->argv(), 1 );
00039 }
00040 
00041 OptionParser::OptionParser( int offset ) {
00042     QCoreApplication* qApp1 = QCoreApplication::instance();
00043     if ( !qApp1 ) {
00044        qFatal( "OptionParser: requires a QApplication instance to be constructed first" );
00045     }
00046     init( qApp1->argc(), qApp1->argv(), offset );
00047 }
00048 
00049 OptionParser::OptionParser( int argc, char *argv[] ) {
00050     init( argc, argv );
00051 }
00052 
00053 OptionParser::OptionParser( const QStringList &a )
00054     : args( a ) {
00055     init( 0, 0 );
00056 }
00057 
00058 void OptionParser::init( int argc, char *argv[], int offset ) {
00059     numReqArgs = numOptArgs = 0;
00060     currArg = 1; // appname is not part of the arguments
00061     if ( argc ) {
00062         // application name
00063         aname = QFileInfo( QString::fromUtf8( argv[0] ) ).fileName();
00064         // arguments
00065         for ( int i = offset; i < argc; ++i ) {
00066             args.append( QString::fromUtf8( argv[i] ) );
00067         }
00068     }
00069 }
00070 
00071 
00072 bool OptionParser::parse( bool untilFirstSwitchOnly ) {
00073     //    qDebug( "parse(%s)", args.join( QString( "," ) ).ascii() );
00074     // push all arguments as we got them on a stack
00075     // more pushes might following when parsing condensed arguments
00076     // like --key=value.
00077     QStack<QString> stack;
00078     {
00079         QStringListIterator it(args);
00080         it.toBack();
00081         while( it.hasPrevious() ) {
00082             stack.push( it.previous() );
00083         }
00084     }
00085 
00086     const OptionConstIterator obegin = options.begin();
00087     const OptionConstIterator oend = options.end();
00088     enum { StartState, ExpectingState, OptionalState } state = StartState;
00089     Option currOpt;
00090     enum TokenType { LongOpt, ShortOpt, Arg, End } t, currType = End;
00091     bool extraLoop = true; // we'll do an extra round. fake an End argument
00092     while ( !stack.isEmpty() || extraLoop ) {
00093         QString a;
00094         QString origA;
00095         // identify argument type
00096         if ( !stack.isEmpty() ) {
00097             a = stack.pop();
00098             currArg++;
00099             origA = a;
00100             //      qDebug( "popped %s", a.ascii() );
00101             if ( a.startsWith( QString::fromLatin1( "--" ) ) ) {
00102                 // recognized long option
00103                 a = a.mid( 2 );
00104                 if ( a.isEmpty() ) {
00105                     qWarning( "'--' feature not supported, yet" );
00106                     exit( 2 );
00107                 }
00108                 t = LongOpt;
00109                 // split key=value style arguments
00110                 int equal = a.indexOf( '=' );
00111                 if ( equal >= 0 ) {
00112                     stack.push( a.mid( equal + 1 ) );
00113                     currArg--;
00114                     a = a.left( equal );
00115                 }
00116             } else if ( a.length() == 1 ) {
00117                 t = Arg;
00118             } else if ( a[0] == '-' ) {
00119 #if 0 // compat mode for -long style options
00120                 if ( a.length() == 2 ) {
00121                     t = ShortOpt;
00122                     a = a[1];
00123                 } else {
00124                     a = a.mid( 1 );
00125                     t = LongOpt;
00126                     // split key=value style arguments
00127                     int equal = a.find( '=' );
00128                     if ( equal >= 0 ) {
00129                         stack.push( a.mid( equal + 1 ) );
00130                         currArg--;
00131                         a = a.left( equal );
00132                     }
00133                 }
00134 #else
00135                 // short option
00136                 t = ShortOpt;
00137                 // followed by an argument ? push it for later processing.
00138                 if ( a.length() > 2 ) {
00139                     stack.push( a.mid( 2 ) );
00140                     currArg--;
00141                 }
00142                 a = a[1];
00143 #endif
00144             } else {
00145                 t = Arg;
00146             }
00147         } else {
00148             // faked closing argument
00149             t = End;
00150         }
00151         // look up among known list of options
00152         Option opt;
00153         if ( t != End ) {
00154             OptionConstIterator oit = obegin;
00155             while ( oit != oend ) {
00156                 const Option &o = *oit;
00157                 if ( ( t == LongOpt && a == o.lname ) || // ### check state
00158                     ( t == ShortOpt && a[0].unicode() == o.sname ) ) {
00159                     opt = o;
00160                     break;
00161                 }
00162                 ++oit;
00163             }
00164             if ( t == LongOpt && opt.type == OUnknown ) {
00165                 if ( currOpt.type != OVarLen ) {
00166                     qWarning( "Unknown option --%s", a.toAscii().data() );
00167                     return false;
00168                 } else {
00169                     // VarLength options support arguments starting with '-'
00170                     t = Arg;
00171                 }
00172             } else if ( t == ShortOpt && opt.type == OUnknown ) {
00173                 if ( currOpt.type != OVarLen ) {
00174                     qWarning( "Unknown option -%c", a[0].unicode() );
00175                     return false;
00176                 } else {
00177                     // VarLength options support arguments starting with '-'
00178                     t = Arg;
00179                 }
00180             }
00181         } else {
00182             opt = Option( OEnd );
00183         }
00184 
00185         // interpret result
00186         switch ( state ) {
00187         case StartState:
00188             if ( opt.type == OSwitch ) {
00189                 setSwitch( opt );
00190                 setOptions.insert( opt.lname, 1 );
00191                 setOptions.insert( QString( QChar( opt.sname ) ), 1 );
00192             } else if ( opt.type == OArg1 || opt.type == ORepeat ) {
00193                 state = ExpectingState;
00194                 currOpt = opt;
00195                 currType = t;
00196                 setOptions.insert( opt.lname, 1 );
00197                 setOptions.insert( QString( QChar( opt.sname ) ), 1 );
00198             } else if ( opt.type == OOpt || opt.type == OVarLen ) {
00199                 state = OptionalState;
00200                 currOpt = opt;
00201                 currType = t;
00202                 setOptions.insert( opt.lname, 1 );
00203                 setOptions.insert( QString( QChar( opt.sname ) ), 1 );
00204             } else if ( opt.type == OEnd ) {
00205                 // we're done
00206             } else if ( opt.type == OUnknown && t == Arg ) {
00207                 if ( numReqArgs > 0 ) {
00208                     if ( reqArg.stringValue->isNull() ) { // ###
00209                         *reqArg.stringValue = a;
00210                     } else {
00211                         qWarning( "Too many arguments" );
00212                         return false;
00213                     }
00214                 } else if ( numOptArgs > 0 ) {
00215                     if ( optArg.stringValue->isNull() ) { // ###
00216                         *optArg.stringValue = a;
00217                     } else {
00218                         qWarning( "Too many arguments" );
00219                         return false;
00220                     }
00221                 }
00222             } else {
00223                 qFatal( "unhandled StartState case %d",  opt.type );
00224             }
00225             break; //--- fino a qui ad indentare
00226         case ExpectingState:
00227             if ( t == Arg ) {
00228                 if ( currOpt.type == OArg1 ) {
00229                     *currOpt.stringValue = a;
00230                     state = StartState;
00231                 } else if ( currOpt.type == ORepeat ) {
00232                     currOpt.listValue->append( a );
00233                     state = StartState;
00234                 } else {
00235                     abort();
00236                 }
00237             } else {
00238                 QString n = currType == LongOpt ?
00239                     currOpt.lname : QString( QChar( currOpt.sname ) );
00240                 qWarning( "Expected an argument after '%s' option", n.toAscii().data() );
00241                 return false;
00242             }
00243             break;
00244         case OptionalState:
00245             if ( t == Arg ) {
00246                 if ( currOpt.type == OOpt ) {
00247                     *currOpt.stringValue = a;
00248                     state = StartState;
00249                 } else if ( currOpt.type == OVarLen ) {
00250                     currOpt.listValue->append( origA );
00251                     // remain in this state
00252                 } else {
00253                     abort();
00254                 }
00255             } else {
00256                 // optional argument not specified
00257                 if ( currOpt.type == OOpt )
00258                     *currOpt.stringValue = currOpt.def;
00259                 if ( t != End ) {
00260                     // re-evaluate current argument
00261                     stack.push( origA );
00262                     currArg--;
00263                 }
00264                 state = StartState;
00265             }
00266             break;
00267         }
00268 
00269         if ( untilFirstSwitchOnly && opt.type == OSwitch )
00270             return true;
00271 
00272         // are we in the extra loop ? if so, flag the final end
00273         if ( t == End )
00274             extraLoop = false;
00275     }
00276 
00277     if ( numReqArgs > 0 && reqArg.stringValue->isNull() ) {
00278         qWarning( "Lacking required argument" );
00279         return false;
00280     }
00281 
00282     return true;
00283 }
00284 
00285 void OptionParser::addOption( Option o ) {
00286     // ### check for conflicts
00287     options.append( o );
00288 }
00289 
00290 void OptionParser::addSwitch( const QString &lname, bool *b ) {
00291     Option opt( OSwitch, 0, lname );
00292     opt.boolValue = b;
00293     addOption( opt );
00294     // ### could do all inits at the beginning of parse()
00295     *b = false;
00296 }
00297 
00298 void OptionParser::setSwitch( const Option &o ) {
00299     assert( o.type == OSwitch );
00300     *o.boolValue = true;
00301 }
00302 
00303 void OptionParser::addOption( char s, const QString &l, QString *v ) {
00304     Option opt( OArg1, s, l );
00305     opt.stringValue = v;
00306     addOption( opt );
00307     *v = QString::null;
00308 }
00309 
00310 void OptionParser::addVarLengthOption( const QString &l, QStringList *v ) {
00311     Option opt( OVarLen, 0, l );
00312     opt.listValue = v;
00313     addOption( opt );
00314     *v = QStringList();
00315 }
00316 
00317 void OptionParser::addRepeatableOption( char s, QStringList *v ) {
00318     Option opt( ORepeat, s, QString::null );
00319     opt.listValue = v;
00320     addOption( opt );
00321     *v = QStringList();
00322 }
00323 
00324 void OptionParser::addRepeatableOption( const QString &l, QStringList *v ) {
00325     Option opt( ORepeat, 0, l );
00326     opt.listValue = v;
00327     addOption( opt );
00328     *v = QStringList();
00329 }
00330 
00331 void OptionParser::addOptionalOption( const QString &l, QString *v, const QString &def ) {
00332     addOptionalOption( 0, l, v, def );
00333 }
00334 
00335 void OptionParser::addOptionalOption( char s, const QString &l, QString *v, const QString &def ) {
00336     Option opt( OOpt, s, l );
00337     opt.stringValue = v;
00338     opt.def = def;
00339     addOption( opt );
00340     *v = QString::null;
00341 }
00342 
00343 void OptionParser::addArgument( const QString &name, QString *v ) {
00344     Option opt( OUnknown, 0, name );
00345     opt.stringValue = v;
00346     reqArg = opt;
00347     ++numReqArgs;
00348     *v = QString::null;
00349 }
00350 
00351 void OptionParser::addOptionalArgument( const QString &name, QString *v ) {
00352     Option opt( OUnknown, 0, name );
00353     opt.stringValue = v;
00354     optArg = opt;
00355     ++numOptArgs;
00356     *v = QString::null;
00357 }
00358 
00359 
00360 bool OptionParser::isSet( const QString &name ) const {
00361     return setOptions.find( name ) != setOptions.end();
00362 }
00363 
00364 } // end namespace farsa
00365