text: //-------------------------------------------------------------------------
  // Desc: Command line argument parser
  // Tabs: 3
  //
  // Copyright (c) 2001, 2003, 2005-2007 Novell, Inc. All Rights Reserved.
  //
  // This library is free software; you can redistribute it and/or
  // modify it under the terms of the GNU Lesser General Public
  // License as published by the Free Software Foundation; version 2.1
  // of the License.
  //
  // This library is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
  // Library Lesser General Public License for more details.
  //
  // You should have received a copy of the GNU Lesser General Public
  // License along with this library; if not, contact Novell, Inc.
  //
  // To contact Novell about this file by physical or electronic mail, 
  // you may find current contact information at www.novell.com.
  //
  // $Id$
  //------------------------------------------------------------------------------
  
  #include "ftksys.h"
  
  FSTATIC FLMBOOL matchesAtParams(
  	const char *	pszStr);
  
  FSTATIC FLMBOOL matchesEqualsAtParams(
  	const char *	pszStr);
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  class F_Arg : public F_Object
  {
  private:
  
  	F_Arg(
  		const char *			pszIdentifier,
  		const char *			pszShortHelp,
  		FLMBOOL					bCaseSensitive,
  		F_ARG_TYPE				argType,
  		F_ARG_CONTENT_TYPE	contentType);		
  	
  	virtual ~F_Arg();
  
  	const char * getIdentifier( void)
  	{
  		return( m_pszIdentifier);
  	}
  	
  	FLMBOOL isPresent( void)
  	{
  		return( m_bIsPresent);
  	}
  	
  	FLMUINT getValueCount( void)
  	{
  		return( m_uiValueCount);
  	}
  	
  	FLMBOOL getCaseSensitive( void)
  	{
  		return( m_bCaseSensitive);
  	}
  	
  	const char * getShortHelp( void)
  	{
  		return( m_pszShortHelp);
  	}
  	
  	F_ARG_TYPE getArgType( void)
  	{
  		return( m_argType);
  	}
  	
  	F_ARG_CONTENT_TYPE getContentType( void)
  	{
  		return( m_contentType);
  	}
  	
  	F_ARG_VALIDATOR getValidator( void)
  	{
  		return( m_validator);
  	}
  	
  	void * getValidatorData( void)
  	{
  		return( m_pvValidatorData);
  	}
  	
  	F_Vector * getStringSet( void)
  	{
  		return( &m_stringSet);
  	}
  	
  	FLMUINT getStringSetLen( void)
  	{
  		return( m_uiStringSetCount);
  	}
  	
  	void getMinMax(
  		FLMUINT *			puiMin,
  		FLMUINT *			puiMax);
  		
  	void getMinMax( 
  		FLMINT *				puiMin,
  		FLMINT *				puiMax);
  
  	FLMUINT getUINT(
  		FLMUINT				uiIndex);
  		
  	FLMINT getINT(
  		FLMUINT				uiIndex);
  		
  	FLMBOOL getBOOL(
  		FLMUINT				uiIndex);
  		
  	const char * getString(
  		FLMUINT				uiIndex);
  		
  	void getString(
  		char *				pszDestination,
  		FLMUINT				uiDestinationBufferSize,
  		FLMUINT				uiIndex);
  
  	void setPresent( void)
  	{
  		m_bIsPresent = TRUE;
  	}
  	
  	RCODE addValue( 
  		const char *		pszVal);
  		
  	const char * getValue(
  		FLMUINT				uiIndex);
  		
  	void setValidator( 
  		F_ARG_VALIDATOR	validator,
  		void *				pvValidatorData)
  	{
  		m_validator = validator;
  		m_pvValidatorData = pvValidatorData;
  	}
  	
  	void setMinMax(
  		FLMUINT				uiMin,
  		FLMUINT				uiMax)
  	{
  		m_uiMin = uiMin;
  		m_uiMax = uiMax;
  	}
  	
  	void setMinMax( 
  		FLMINT				iMin,
  		FLMINT				iMax)
  	{
  		m_iMin = iMin;
  		m_iMax = iMax;
  	}
  	
  	RCODE addToStringSet(
  		const char *		pszStr);
  
  	const char *			m_pszIdentifier;
  	const char *			m_pszShortHelp;
  	FLMBOOL					m_bCaseSensitive;
  	F_ARG_TYPE				m_argType;
  	F_ARG_CONTENT_TYPE	m_contentType;
  	F_Vector					m_valuesVec;
  	FLMUINT					m_uiValueCount;
  	FLMBOOL					m_bIsPresent;
  	F_ARG_VALIDATOR		m_validator;
  	void *					m_pvValidatorData;
  	FLMUINT					m_uiMin;
  	FLMUINT					m_uiMax;
  	FLMINT					m_iMin;
  	FLMINT					m_iMax;
  	F_Vector					m_stringSet;
  	FLMUINT					m_uiStringSetCount;
  	
  friend class F_ArgSet;
  };
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  F_Arg::F_Arg(
  	const char *			pszIdentifier,
  	const char *			pszShortHelp,
  	FLMBOOL					bCaseSensitive,
  	F_ARG_TYPE				argType,
  	F_ARG_CONTENT_TYPE	contentType)		
  {
  	m_pszIdentifier = pszIdentifier;
  	m_pszShortHelp = pszShortHelp;
  	m_bCaseSensitive = bCaseSensitive;
  	m_argType = argType;
  	m_contentType = contentType;
  		
  	m_uiValueCount = 0;
  	m_bIsPresent = FALSE;
  
  	m_validator = NULL;
  	m_uiMin = 0xFFFFFFFF;
  	m_uiMax = 0xFFFFFFFF;
  	m_iMin = -1;
  	m_iMax = -1;
  	m_uiStringSetCount = 0;
  }
  	
  /****************************************************************************
  Desc:
  ****************************************************************************/
  F_Arg::~F_Arg()
  {
  	FLMUINT		uiKill;
  	char *		pszStr;
  
  	for( uiKill = 0; uiKill < m_uiValueCount; uiKill++)
  	{
  		pszStr = (char *)(m_valuesVec.getElementAt( uiKill));
  		
  		if( pszStr)
  		{
  			f_free( &pszStr);
  		}
  	}
  }
  	
  /****************************************************************************
  Desc:
  ****************************************************************************/
  RCODE F_Arg::addValue(
  	const char *	pszVal)
  {
  	RCODE				rc = NE_FLM_OK;
  	char *			pszNewVal = NULL;
  	
  	switch( getContentType())
  	{
  		case F_ARG_CONTENT_SIGNED_INT:
  		{
  			// Allow escaped minus, i.e. foo \-1 to pass through a -1
  			// and not have it read as an option.	Here, we detect that
  			// it made it through, so increment past the '-'
  			
  			if( pszVal[ 0] == '\\')
  			{
  				pszVal++;
  			}
  			
  			break;
  		}
  		
  		default:
  		{
  			break;
  		}
  	}
  	
  	if( RC_BAD( rc = f_alloc( f_strlen( pszVal) + 1, &pszNewVal)))
  	{
  		goto Exit;
  	}
  	
  	f_strcpy( pszNewVal, pszVal);
  	
  	if( RC_BAD( rc = m_valuesVec.setElementAt( pszNewVal, m_uiValueCount)))
  	{
  		goto Exit;
  	}
  	
  	pszNewVal = NULL;
  	m_uiValueCount++;
  
  Exit:
  
  	if( pszNewVal)
  	{
  		f_free( &pszNewVal);
  	}
  	
  	return( rc);
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  const char * F_Arg::getValue(
  	FLMUINT			uiIndex)
  {
  	f_assert( uiIndex < getValueCount());
  	
  	return( (const char *)(m_valuesVec.getElementAt( uiIndex)));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  RCODE F_Arg::addToStringSet(
  	const char *		pszStr)
  {
  	RCODE		rc = NE_FLM_OK;
  	
  	if( RC_BAD( rc = m_stringSet.setElementAt( 
  		(void *)pszStr, m_uiStringSetCount)))
  	{
  		goto Exit;
  	}
  	
  	m_uiStringSetCount++;
  	
  Exit:
  
  	return( rc);
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  void F_Arg::getMinMax( FLMUINT * puiMin, FLMUINT * puiMax)
  {
  	f_assert( getContentType() == F_ARG_CONTENT_UNSIGNED_INT);
  	f_assert( puiMin && puiMax);
  	
  	*puiMin = m_uiMin;
  	*puiMax = m_uiMax;
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  void F_Arg::getMinMax( FLMINT * piMin, FLMINT * piMax)
  {
  	f_assert( getContentType() == F_ARG_CONTENT_SIGNED_INT);
  	f_assert( piMin && piMax);
  	
  	*piMin = m_iMin;
  	*piMax = m_iMax;
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMUINT F_Arg::getUINT( FLMUINT uiIndex)
  {
  	return( f_atoud( (const char *)m_valuesVec.getElementAt( uiIndex)));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMINT F_Arg::getINT( FLMUINT uiIndex)
  {
  	return( f_atoi( (const char *)m_valuesVec.getElementAt( uiIndex)));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMBOOL F_Arg::getBOOL( FLMUINT uiIndex)
  {
  	return( f_atobool( (const char *)m_valuesVec.getElementAt( uiIndex)));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  const char * F_Arg::getString( FLMUINT uiIndex)
  {
  	return( (const char *)(m_valuesVec.getElementAt( uiIndex)));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  void F_Arg::getString(
  	char *			pszDestination,
  	FLMUINT			uiDestinationBufferSize,
  	FLMUINT			uiIndex)
  {
  	const char * 	pszStr = (const char *)m_valuesVec.getElementAt( uiIndex);
  	
  	f_strncpy( pszDestination, pszStr, uiDestinationBufferSize - 1);
  	pszDestination[ uiDestinationBufferSize - 1] = 0;
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  F_ArgSet::F_ArgSet()
  {
  	m_uiArgVecIndex = 0;
  	m_uiArgc = 0;
  	m_pArgv = NULL;
  	m_pRepeatingArg = NULL;
  	m_uiOptionsVecLen = 0;
  	m_uiRequiredArgsVecLen = 0;
  	m_uiOptionalArgsVecLen = 0;
  	m_szExecBaseName[ 0] = 0;
  	m_pPrintfClient = NULL;
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  F_ArgSet::~F_ArgSet()
  {
  	FLMUINT 		uiKill;
  	
  	if( m_pArgv)
  	{
  		// Kill any dynamically allocated memory for the processed
  		// command line args
  		
  		for( uiKill = 0; uiKill < m_uiArgc; uiKill++)
  		{
  			const char * 	pszStr = (const char *)m_pArgv->getElementAt( uiKill);
  			
  			if( pszStr)
  			{
  				f_free( &pszStr);
  			}
  		}
  		
  		m_pArgv->Release();
  		m_pArgv = NULL;
  	}
  
  	// Kill the dynamically allocated F_Arg objs
  	
  	for( uiKill = 0; uiKill < m_uiArgVecIndex; uiKill++)
  	{
  		F_Arg *	pArg = (F_Arg *)(m_argVec.getElementAt( uiKill));
  		
  		if( pArg)
  		{
  			pArg->Release();
  		}
  	}
  	
  	if( m_pPrintfClient)
  	{
  		m_pPrintfClient->Release();
  	}
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  RCODE FTKAPI F_ArgSet::setup(
  	IF_PrintfClient *						pPrintfClient)
  {
  	RCODE			rc = NE_FLM_OK;
  	
  	if( (m_pPrintfClient = pPrintfClient) != NULL)
  	{
  		m_pPrintfClient->AddRef();
  	}
  	
  	return( rc);
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  RCODE F_ArgSet::addArg(
  	const char *			pszIdentifier,
  	const char *			pszShortHelp,
  	FLMBOOL					bCaseSensitive,
  	F_ARG_TYPE				argType,
  	F_ARG_CONTENT_TYPE	contentType
  	...)
  {
  	RCODE				rc = NE_FLM_OK;
  	FLMUINT			uiLoop;
  	const char *	pszExistingStr;
  	F_Arg *			pNewArg = NULL;
  
  	// Must have an identifier that's no longer than this, for the
  	// printUsage() to display on-screen correctly
  	
  	f_assert( f_strlen( pszIdentifier) <= 16);
  
  	// Options with argumens need even shorter identifiers
  	
  	if( (argType == F_ARG_OPTION) && (contentType != F_ARG_CONTENT_NONE))
  	{
  		f_assert( f_strlen( pszIdentifier) <= 12);
  	}
  
  	// Identifier cannot contain '='
  	
  	f_assert( !f_strchr( pszIdentifier, '='));
  	
  	// Validate that it's not there already based on the identifier
  	
  	for( uiLoop = 0; uiLoop < m_uiArgVecIndex; uiLoop++)
  	{
  		pszExistingStr =
  			((F_Arg *)m_argVec.getElementAt( uiLoop))->getIdentifier();
  			
  		if( f_strcmp( pszExistingStr, pszIdentifier) == 0)
  		{
  			// Duplicate arg, duplicate identifier found.  If you are
  			// designing a utility and you got this error, it means you
  			// passed the same first argument to addArg() twice.
  			
  			f_assert( 0);
  		}
  	}
  
  	// Enforce order of calling addArg() so it matches the way a utility
  	// is called on a command line.  This is so a utility is coded up
  	// correctly, since the order matters.
  	
  	if( argType == F_ARG_REQUIRED_ARG)
  	{
  		// You have to add required args first, then optionally optional
  		// args, then optionally a repeating arg.  The rule is:
  		// required args -> optional args -> repeating arg.
  		//
  		// While all 3 of these types are optional in themselves, you
  		// can't have a required arg added after the next two, or
  		// an optional arg added after a repeating arg.
  		
  		f_assert( (m_uiOptionalArgsVecLen == 0) && (!m_pRepeatingArg));
  	}
  	else if( argType == F_ARG_OPTIONAL_ARG)
  	{
  		// Can't add an optional arg after adding a repeating arg...the
  		// order matters!
  		
  		f_assert( !m_pRepeatingArg); 
  	}
  
  	// Can't have an embedded newline in the short help or it will
  	// mess up the word-wrapping in displayShortHelpLines
  	
  	f_assert( !f_strchr( pszShortHelp, '\n'));
  
  	if( (pNewArg = f_new F_Arg( pszIdentifier, pszShortHelp, bCaseSensitive,
  						argType, contentType)) == NULL)
  	{
  		rc = RC_SET( NE_FLM_MEM);
  		goto Exit;
  	}
  
  	switch( contentType)
  	{
  		case F_ARG_CONTENT_VALIDATOR:
  		{
  			f_va_list 			args;
  			F_ARG_VALIDATOR 	validator;
  			void * 				pvValidatorData;
  			
  			f_va_start( args, contentType);
  			
  			validator = f_va_arg( args, F_ARG_VALIDATOR);
  			pvValidatorData = f_va_arg( args, void *);
  			pNewArg->setValidator( validator, pvValidatorData);
  			
  			f_va_end( args);
  			break;
  		}
  		
  		case F_ARG_CONTENT_SIGNED_INT:
  		{
  			f_va_list 			args;
  			FLMINT 				iMin;
  			FLMINT 				iMax;
  			
  			f_va_start( args, contentType);
  			
  			iMin = f_va_arg( args, FLMINT);
  			iMax = f_va_arg( args, FLMINT);
  			pNewArg->setMinMax( iMin, iMax);
  			
  			f_va_end( args);
  			break;
  		}
  		
  		case F_ARG_CONTENT_UNSIGNED_INT:
  		{
  			f_va_list 			args;
  			FLMUINT 				uiMin;
  			FLMUINT 				uiMax;
  			
  			f_va_start( args, contentType);
  			
  			uiMin = f_va_arg( args, FLMUINT);
  			uiMax = f_va_arg( args, FLMUINT);
  			
  			f_assert( uiMin <= uiMax);
  			pNewArg->setMinMax( uiMin, uiMax);
  			
  			f_va_end( args);
  			break;
  		}
  		
  		case F_ARG_CONTENT_ALLOWED_STRING_SET:
  		{
  			f_va_list 			args;
  			
  			f_va_start( args, contentType);
  			
  			for( ;;)
  			{
  				const char *	pszNext = f_va_arg( args, char *);
  				
  				if ( !pszNext)
  				{
  					break;
  				}
  				
  				if( RC_BAD( rc = pNewArg->addToStringSet( pszNext)))
  				{
  					goto Exit;
  				}
  			}
  			
  			f_va_end( args);
  		}
  		
  		case F_ARG_CONTENT_EXISTING_FILE:
  		case F_ARG_CONTENT_NONE:
  		case F_ARG_CONTENT_BOOL:
  		case F_ARG_CONTENT_STRING:
  		{
  			break;
  		}
  		
  		default:
  		{
  			f_assert( 0);
  			break;
  		}
  	}
  
  	// Store the specific types of args in their own vector (and pointer
  	// in the case of m_pRepeatingArg)
  	
  	switch( argType)
  	{
  		case F_ARG_OPTION:
  		{
  			if( RC_BAD( rc = m_optionsVec.setElementAt(
  				pNewArg, m_uiOptionsVecLen)))
  			{
  				goto Exit;
  			}
  			
  			m_uiOptionsVecLen++;
  			break;
  		}
  		
  		case F_ARG_REQUIRED_ARG:
  		{
  			if( RC_BAD( rc = m_requiredArgsVec.setElementAt(
  				pNewArg, m_uiRequiredArgsVecLen)))
  			{
  				goto Exit;
  			}
  			
  			m_uiRequiredArgsVecLen++;
  			break;
  		}
  		
  		case F_ARG_OPTIONAL_ARG:
  		{
  			if( RC_BAD( rc = m_optionalArgsVec.setElementAt(
  				pNewArg, m_uiOptionalArgsVecLen)))
  			{
  				goto Exit;
  			}
  			
  			m_uiOptionalArgsVecLen++;
  			break;
  		}
  		
  		case F_ARG_REPEATING_ARG:
  		{
  			// Cannot have multiple repeating args
  			
  			f_assert( !m_pRepeatingArg);
  			m_pRepeatingArg = pNewArg;
  			break;
  		}
  		
  		default:
  		{
  			f_assert( 0);
  			break;
  		}
  	}
  
  	// Store all args in a vector
  	
  	if( RC_BAD( rc = m_argVec.setElementAt( pNewArg, m_uiArgVecIndex)))
  	{
  		goto Exit;
  	}
  
  	pNewArg = NULL;	
  	m_uiArgVecIndex++;
  	
  Exit:
  
  	if( pNewArg)
  	{
  		pNewArg->Release();
  	}
  
  	return( rc);
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  F_Arg * F_ArgSet::getArg(
  	const char *	pszIdentifier)
  {
  	F_Arg *			pArg;
  	FLMUINT			uiLoop;
  	
  	for( uiLoop = 0; uiLoop < m_uiArgVecIndex; uiLoop++)
  	{
  		pArg = (F_Arg *)(m_argVec.getElementAt( uiLoop));
  		
  		if( f_strcmp( pArg->getIdentifier(), pszIdentifier) == 0)
  		{
  			return( pArg);
  		}
  	}
  	
  	f_assert( 0);
  	return( NULL);
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMBOOL F_ArgSet::needMoreArgs( 
  	F_Vector * 		pVec,
  	FLMUINT 			uiVecLen)
  {
  	FLMUINT			uiLoop;
  	
  	f_assert( pVec);
  	
  	for( uiLoop = 0; uiLoop < uiVecLen; uiLoop++)
  	{
  		F_Arg * 		pArg = ((F_Arg *)(pVec->getElementAt( uiLoop)));
  		
  		// If at least one arg is non-present, we need some more.
  		
  		if( !pArg->isPresent())
  		{
  			return( TRUE);
  		}
  	}
  	
  	return( FALSE);
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  void F_ArgSet::dump( 
  	F_Vector * 		pVec, 
  	FLMUINT 			uiVecLen)
  {
  	FLMUINT			uiLoop;
  	
  	// Loop through the args and print out a table for easy reference to see
  	// what was set and what wasn't
  	
  	for( uiLoop = 0; uiLoop < uiVecLen; uiLoop++)
  	{
  		F_Arg * 	pArg = (F_Arg *)(pVec->getElementAt( uiLoop));
  		
  		f_printf( m_pPrintfClient, "	");
  		f_printf( m_pPrintfClient, pArg->getIdentifier());
  		f_printf( m_pPrintfClient, ": ");
  		
  		if( pArg->isPresent())
  		{
  			f_printf( m_pPrintfClient, "values={");
  					
  			for( FLMUINT uiVals = 0; uiVals < pArg->getValueCount(); uiVals++)
  			{
  				f_printf( m_pPrintfClient, pArg->getValue( uiVals));
  				
  				if( uiVals != (pArg->getValueCount() - 1))
  				{
  					f_printf( m_pPrintfClient, ",");
  				}
  			}
  			
  			f_printf( m_pPrintfClient, "}\n");
  		}
  		else
  		{
  			f_printf( m_pPrintfClient, "not supplied\n");
  		}
  	}
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  RCODE F_ArgSet::parseOption( 
  	const char *	pszArg) 
  {
  	RCODE				rc = NE_FLM_OK;
  	const char *	pszArgArg;
  	FLMBOOL			bFoundMatch = FALSE;
  	FLMUINT			uiTraverseOpts;
  	
  	pszArg++;
  	pszArgArg = f_strchr( (const char *)pszArg, '=');
  	
  	for( uiTraverseOpts = 0; 
  		  uiTraverseOpts < m_uiOptionsVecLen;
  		  uiTraverseOpts++)
  	{
  		F_Arg *		pArg = (F_Arg *)m_optionsVec.getElementAt( uiTraverseOpts);
  		FLMBOOL		bCaseSensitive = pArg->getCaseSensitive();
  		
  		if( (bCaseSensitive && (f_strcmp( pArg->getIdentifier(), pszArg) == 0)) ||
  			(!bCaseSensitive && (f_stricmp( pArg->getIdentifier(), pszArg) == 0)))
  		{
  			if( pArg->getContentType() != F_ARG_CONTENT_NONE)
  			{
  				f_printf( m_pPrintfClient, "ERROR: Option '");
  				f_printf( m_pPrintfClient, pArg->getIdentifier());
  				f_printf( m_pPrintfClient, "' requires argument of the form option=value");
  				
  				printUsage();
  				
  				rc = RC_SET( NE_FLM_FAILURE);
  				goto Exit;
  			}
  			else
  			{
  				bFoundMatch = TRUE;
  				pArg->setPresent();
  				break;
  			}
  		}
  		else if( pszArgArg)
  		{
  			f_assert( pszArgArg[ 0] == '=');
  			
  			FLMBOOL bIsMatch =
  				((bCaseSensitive &&
  					(f_strncmp( pArg->getIdentifier(), pszArg, pszArgArg - pszArg) == 0)) ||
  					(!bCaseSensitive &&
  						(f_strnicmp( pArg->getIdentifier(), pszArg, pszArgArg - pszArg) == 0)))
  				? TRUE
  				: FALSE;
  
  			if( bIsMatch)
  			{
  				pszArgArg++;
  				
  				if( pArg->getContentType() == F_ARG_CONTENT_NONE)
  				{
  					f_printf( m_pPrintfClient,
  						"ERROR: Cannot give argument to option ");
  					f_printf( m_pPrintfClient, pArg->getIdentifier());
  						
  					printUsage();
  					
  					rc = RC_SET( NE_FLM_FAILURE);
  					goto Exit;
  				}
  				else
  				{
  					if( RC_BAD( rc = pArg->addValue( pszArgArg)))
  					{
  						goto Exit;
  					}
  					
  					pArg->setPresent();
  					bFoundMatch = TRUE;
  					break;
  				}
  			}
  		}
  	}
  	
  	if ( !bFoundMatch)
  	{
  		f_printf( m_pPrintfClient, "Unknown option ");
  		f_printf( m_pPrintfClient, pszArg);
  		f_printf( m_pPrintfClient, "\n");
  		
  		printUsage();
  		
  		rc = RC_SET( NE_FLM_FAILURE);
  		goto Exit;
  	}
  	
  Exit:
  
  	return( rc); 
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  RCODE F_ArgSet::parseCommandLine(
  	FLMUINT				uiArgc, 
  	const char **		ppszArgv)
  {
  	RCODE					rc = NE_FLM_OK;
  	FLMUINT				uiLoop;
  	FLMUINT				uiArgs;
  	FLMBOOL				bNegative;
  	const char *		pszExecStr = ppszArgv[ 0];
  	char					szDir[ F_PATH_MAX_SIZE];
  	IF_FileSystem *	pFileSystem = NULL;
  
  	// Set up members we'll need
  	
  	if( !m_pArgv)
  	{
  		if( (m_pArgv = f_new F_Vector) == NULL)
  		{
  			rc = RC_SET( NE_FLM_MEM);
  			goto Exit;
  		}
  	}
  	
  	// Read file exec basename into variable
  	
  	if ( RC_BAD( rc = f_pathReduce( pszExecStr, szDir, m_szExecBaseName)))
  	{
  		goto Exit;
  	}
  	
  	// Copy inital args into member vars
  	
  	for( uiLoop = 0; uiLoop < uiArgc; uiLoop++)
  	{
  		const char *	pszCopyThis = ppszArgv[ uiLoop];
  		char *			pszNewStr;
  
  		if( RC_BAD( rc = f_alloc( f_strlen( pszCopyThis) + 1, &pszNewStr)))
  		{
  			goto Exit;
  		}
  
  		f_strcpy( pszNewStr, pszCopyThis);
  		
  		if( RC_BAD( rc = m_pArgv->setElementAt( pszNewStr, uiLoop)))
  		{
  			f_free( &pszNewStr);
  			goto Exit;
  		}
  		
  		m_uiArgc++;
  	}
  	
  	// Pre-process params
  	
  	if ( RC_BAD( rc = preProcessParams()))
  	{
  		goto Exit;
  	}
  
  	// Now read through the args.  They will all be well-formed at this
  	// point.  Look for strings beginning with a hyphen to find options,
  	// and everything else is either a required, optional, or repeating
  	// arg.  Ignore arg 0, which is the executable name.
  	
  	for( uiLoop = 1; uiLoop < m_uiArgc; uiLoop++)
  	{
  		const char *	pszArg = (const char *)m_pArgv->getElementAt( uiLoop);
  		
  		f_assert( f_strlen( pszArg) > 0);
  
  		// Handle help strings first
  		
  		if ( f_stricmp( pszArg, "-h") == 0 ||
  			f_stricmp( pszArg, "-help") == 0 ||
  			f_strcmp( pszArg, "-?") == 0 ||
  			f_strcmp( pszArg, "?") == 0)
  		{
  			printUsage();
  			
  			rc = RC_SET( NE_FLM_FAILURE);
  			goto Exit;
  		}
  		else if( pszArg[ 0] == '-')
  		{
  			// Option-handling is sufficiently complex that we'll handle it
  			// elsewhere in its own function
  			
  			if ( RC_BAD( rc = parseOption( pszArg)))
  			{
  				goto Exit;
  			}
  		}
  		else if( (m_uiRequiredArgsVecLen > 0) &&
  			needMoreArgs( &m_requiredArgsVec, m_uiRequiredArgsVecLen))
  		{
  			// Required argument
  			
  			FLMUINT			uiIndex = 0;
  			F_Arg *			pChosenArg;
  			
  			for( ;;)
  			{
  				pChosenArg = (F_Arg *)m_requiredArgsVec.getElementAt( uiIndex);
  				
  				if ( !pChosenArg->isPresent())
  				{
  					break;
  				}
  				
  				uiIndex++;
  			}
  			
  			if( RC_BAD( rc = pChosenArg->addValue( pszArg)))
  			{
  				goto Exit;
  			}
  			
  			pChosenArg->setPresent();
  		}
  		else if( (m_uiOptionalArgsVecLen > 0) &&
  			needMoreArgs( &m_optionalArgsVec, m_uiOptionalArgsVecLen))
  		{
  			// Optional argument
  			
  			FLMUINT			uiIndex = 0;
  			F_Arg * 			pChosenArg;
  			
  			for( ;;)
  			{
  				pChosenArg = (F_Arg *)m_optionalArgsVec.getElementAt( uiIndex);
  				
  				if ( !pChosenArg->isPresent())
  				{
  					break;
  				}
  				
  				uiIndex++;
  			}
  			
  			if( RC_BAD( rc = pChosenArg->addValue( pszArg)))
  			{
  				goto Exit;
  			}
  			
  			pChosenArg->setPresent();
  		}
  		else if ( m_pRepeatingArg)
  		{
  			m_pRepeatingArg->setPresent();
  			
  			if( RC_BAD( rc = m_pRepeatingArg->addValue( pszArg)))
  			{
  				goto Exit;
  			}
  		}
  		else
  		{
  			f_printf( m_pPrintfClient, "invalid extra argument '");
  			f_printf( m_pPrintfClient, pszArg);
  			f_printf( m_pPrintfClient, "'\n");
  			
  			printUsage();
  			
  			rc = RC_SET( NE_FLM_FAILURE);
  			goto Exit;
  		}
  	}
  
  	// We've read through all the args and set things appropriately.  If
  	// we have any required args unset at this point, that's a user
  	// problem so let's report it and abort.
  	
  	if( needMoreArgs( &m_requiredArgsVec, m_uiRequiredArgsVecLen))
  	{
  		for( uiLoop = 0; uiLoop < m_uiRequiredArgsVecLen; uiLoop++)
  		{
  			F_Arg * 	pArg = (F_Arg*)m_requiredArgsVec.getElementAt( uiLoop);
  			
  			if ( !pArg->isPresent())
  			{
  				f_printf( m_pPrintfClient,
  					"ERROR: Did not pass required arg #%u <%s>\n",
  					uiLoop + 1, pArg->getIdentifier());
  				{
  					goto Exit;
  				}
  			}
  		}
  		
  		printUsage();
  
  		rc = RC_SET( NE_FLM_FAILURE);
  		goto Exit;
  	}
  
  	// Need to loop through all args and validate the inputs.  We can use
  	// m_argVec for this (don't need to use the specific groups of args
  	// such as m_requiredArgsVec, m_optionalArgsVec, etc.)
  	
  	for( uiLoop = 0; uiLoop < m_uiArgVecIndex; uiLoop++)
  	{
  		F_Arg * 		pArg = (F_Arg *)(m_argVec.getElementAt( uiLoop));
  		
  		f_assert( pArg);
  		
  		// If it's present, then it has a value we will want to validate
  		
  		if( pArg->isPresent())
  		{
  			FLMBOOL			bValidated = FALSE;
  			const char *	pszStr = "";
  			
  			switch( pArg->getContentType())
  			{
  				case F_ARG_CONTENT_NONE:
  				case F_ARG_CONTENT_STRING:
  				{
  					// Nothing to validate for these, they will always match
  					
  					bValidated = TRUE;
  					break;
  				}
  				
  				case F_ARG_CONTENT_BOOL:
  				{
  					for( uiArgs = 0; uiArgs < pArg->getValueCount(); uiArgs++)
  					{
  						pszStr = pArg->getValue( uiArgs);
  						f_atobool( pszStr, &bValidated);
  						
  						if( !bValidated)
  						{
  							f_printf( m_pPrintfClient, "ERROR: Argument '");
  							f_printf( m_pPrintfClient, pszStr);
  							f_printf( m_pPrintfClient,
  								"' is not a valid representation of a boolean");
  							
  							break;
  						}
  					}
  					
  					break;
  				}
  				
  				case F_ARG_CONTENT_VALIDATOR:
  				{
  					for( uiArgs = 0; uiArgs < pArg->getValueCount(); uiArgs++)
  					{
  						pszStr = pArg->getValue( uiArgs);
  						
  						if( (bValidated = pArg->getValidator()( pszStr, 
  									pArg->getIdentifier(), m_pPrintfClient,
  									pArg->getValidatorData())) == FALSE)
  						{
  							break;
  						}
  					}
  					
  					break;
  				}
  				
  				case F_ARG_CONTENT_SIGNED_INT:
  				case F_ARG_CONTENT_UNSIGNED_INT:
  				{
  					for( uiArgs = 0; uiArgs < pArg->getValueCount(); uiArgs++)
  					{
  						pszStr = pArg->getValue( uiArgs);
  						
  						if( (bValidated = f_isNumber( pszStr, &bNegative)) == FALSE)
  						{
  							break;
  						}
  						
  						if( pArg->getContentType() == F_ARG_CONTENT_UNSIGNED_INT &&
  							bNegative)
  						{
  							bValidated = FALSE;
  							break;
  						}
  						
  						if( pArg->getContentType() == F_ARG_CONTENT_SIGNED_INT)
  						{
  							FLMINT 		iMax;
  							FLMINT		iMin;
  							FLMINT		iArg;
  							
  							pArg->getMinMax( &iMin, &iMax);
  							iArg = f_atoi( pszStr);
  							
  							if( (iArg > iMax) || (iArg < iMin))
  							{
  								f_printf( m_pPrintfClient,
  									"ERROR: Argument '%s' violates range "
  									"requirement of min=%d, max=%d\n",
  									pszStr, iMin, iMax);
  										
  								bValidated = FALSE;
  								break;
  							}
  						}
  						else
  						{
  							FLMUINT 		uiMax;
  							FLMUINT		uiMin;
  							FLMUINT		uiArg;
  							
  							pArg->getMinMax( &uiMin, &uiMax);
  							uiArg = f_atoud( pszStr);
  							
  							if( (uiArg > uiMax) || (uiArg < uiMin))
  							{
  								f_printf( m_pPrintfClient,
  									"ERROR: Argument '%s' violates range "
  									"requirement of min=%u, max=%u\n",
  									pszStr, (unsigned)uiMin, (unsigned)uiMax);
  								
  								bValidated = FALSE;
  								break;
  							}
  						}
  					}
  					
  					break;
  				}
  				
  				case F_ARG_CONTENT_ALLOWED_STRING_SET:
  				{
  					for( uiArgs = 0; uiArgs < pArg->getValueCount(); uiArgs++)
  					{
  						FLMUINT 		uiStrSetLen = pArg->getStringSetLen();
  						FLMUINT 		uiStrSet;
  						FLMBOOL 		bMatched = FALSE;
  						F_Vector * 	pStringSet;
  						
  						pszStr = pArg->getValue( uiArgs);
  						pStringSet = pArg->getStringSet();
  						
  						for( uiStrSet = 0; uiStrSet < uiStrSetLen; uiStrSet++)
  						{
  							bMatched = (f_strcmp( 
  										(const char *)pStringSet->getElementAt( uiStrSet),
  										pszStr) == 0) ? TRUE : FALSE;
  							if( bMatched)
  							{
  								break;
  							}
  						}
  						
  						bValidated = bMatched;
  						
  						if( !bValidated)
  						{
  							f_printf( m_pPrintfClient, "ERROR: '");
  							f_printf( m_pPrintfClient, pszStr);
  							f_printf( m_pPrintfClient,
  								"' is invalid. Must be a member of {");
  							
  							for( uiStrSet = 0; uiStrSet < uiStrSetLen; uiStrSet++)
  							{
  								const char * 	pszNextMember = (const char *)pStringSet->getElementAt( uiStrSet);
  								
  								f_printf( m_pPrintfClient, "'");
  								f_printf( m_pPrintfClient, pszNextMember);
  								f_printf( m_pPrintfClient, "'");
  								
  								if( uiStrSet != (uiStrSetLen - 1))
  								{
  									f_printf( m_pPrintfClient, ",");
  								}
  							}
  							
  							f_printf( m_pPrintfClient, "}\n");
  							break;
  						}
  					}
  					
  					break;
  				}
  				
  				case F_ARG_CONTENT_EXISTING_FILE:
  				{
  					if( RC_OK( FlmGetFileSystem( &pFileSystem)))
  					{
  						for( uiArgs = 0; uiArgs < pArg->getValueCount(); uiArgs++)
  						{
  							pszStr = pArg->getValue( uiArgs);
  							bValidated = RC_OK( pFileSystem->doesFileExist( pszStr));
  							
  							if ( !bValidated)
  							{
  								f_printf( m_pPrintfClient, "ERROR: File ");
  								f_printf( m_pPrintfClient, pszStr);
  								f_printf( m_pPrintfClient, " does not exist\n");
  								break;
  							}
  						}
  						
  						pFileSystem->Release();
  						pFileSystem = NULL;
  					}
  					
  					break;
  				}
  				
  				default:
  				{
  					f_assert( 0);
  				}
  			}
  			
  			if( !bValidated)
  			{
  				f_printf( m_pPrintfClient, "ERROR: Argument '");
  				f_printf( m_pPrintfClient, pArg->getIdentifier());
  				f_printf( m_pPrintfClient, "' did not validate with value '");
  				f_printf( m_pPrintfClient, pszStr);
  				f_printf( m_pPrintfClient, "'\n");
  				
  				printUsage();
  				
  				rc = RC_SET( NE_FLM_FAILURE);
  				goto Exit;
  			}
  		}
  	}
  	
  Exit:
  
  	if( pFileSystem)
  	{
  		pFileSystem->Release();
  	}
  
  	return( rc);
  }
  
  /****************************************************************************
  Desc: Print out the short help lines, breaking up at word boundaries.  This
  		method has hard-coded agreements with printUsage(), so be careful
  		when changing it.
  ****************************************************************************/
  RCODE F_ArgSet::displayShortHelpLines(
  	const char *	pszShortHelp,
  	FLMUINT			uiCharsPerLine)
  {
  	RCODE				rc = NE_FLM_OK;
  	char *			pszClone = NULL;
  	FLMUINT			uiLen = f_strlen( pszShortHelp);
  	FLMUINT			uiLoop = 0;
  	FLMUINT			uiLastGoodBreakingPos = 0;
  
  	if( RC_BAD( rc = f_strdup( pszShortHelp, &pszClone)))
  	{
  		goto Exit;
  	}
  			
  	for( ;;)
  	{
  		if( uiLoop > uiCharsPerLine)
  		{
  			if ( uiLastGoodBreakingPos == 0)
  			{
  				// This means that you made words that were too long in the
  				// short-help.  You need to break up the words so that
  				// this doesn't happen.
  				
  				f_assert( 0);
  				rc = RC_SET( NE_FLM_FAILURE);
  				goto Exit;
  			}
  				
  			// Use uiLastGoodBreakingPos to print out a section
  			
  			pszClone[ uiLastGoodBreakingPos] = 0;
  			
  			f_printf( m_pPrintfClient, pszClone);
  			f_printf( m_pPrintfClient,
  				"\n												");
  			
  			pszClone += f_strlen( pszClone) + 1;
  			uiLoop = 0;
  			uiLen = f_strlen( pszClone);
  			uiLastGoodBreakingPos = 0;
  			
  			continue;
  		}
  		else if( uiLoop == (uiLen - 1))
  		{
  			f_printf( m_pPrintfClient, pszClone);
  			break;
  		}
  		else if( f_isWhitespace( pszClone[ uiLoop]))
  		{
  			uiLastGoodBreakingPos = uiLoop;
  		}
  		
  		uiLoop++;
  	}
  	
  Exit:
  
  	if( pszClone)
  	{
  		f_free( &pszClone);
  	}
  
  	return( rc);
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  void F_ArgSet::printUsage( void)
  {
  	RCODE				rc = NE_FLM_OK;
  	FLMUINT			uiLoop = 0;
  
  	f_printf( m_pPrintfClient, "Usage: ");
  	f_printf( m_pPrintfClient, m_szExecBaseName);
  	
  	if( m_uiOptionsVecLen > 0)
  	{
  		f_printf( m_pPrintfClient, " [OPTIONS]");
  	}
  	
  	if( m_uiRequiredArgsVecLen > 0)
  	{
  		f_printf( m_pPrintfClient, " ");
  	}
  	
  	for( uiLoop = 0; uiLoop < m_uiRequiredArgsVecLen; uiLoop++)
  	{
  		F_Arg * 	pArg = (F_Arg*)m_requiredArgsVec.getElementAt( uiLoop);
  		
  		f_printf( m_pPrintfClient, "<");
  		f_printf( m_pPrintfClient, pArg->getIdentifier());
  		f_printf( m_pPrintfClient, ">");
  		
  		if( uiLoop != (m_uiRequiredArgsVecLen - 1))
  		{
  			f_printf( m_pPrintfClient, " ");
  		}
  	}
  	
  	if( m_uiOptionalArgsVecLen > 0)
  	{
  		f_printf( m_pPrintfClient, " ");
  	}
  	
  	for ( uiLoop = 0; uiLoop < m_uiOptionalArgsVecLen; uiLoop++)
  	{
  		F_Arg * pArg = (F_Arg*)m_optionalArgsVec.getElementAt( uiLoop);
  		
  		f_printf( m_pPrintfClient, "[");
  		f_printf( m_pPrintfClient, pArg->getIdentifier());
  		f_printf( m_pPrintfClient, "]");
  		
  		if( uiLoop != (m_uiOptionalArgsVecLen - 1))
  		{
  			f_printf( m_pPrintfClient, " ");
  		}
  	}
  	
  	if ( m_pRepeatingArg)
  	{
  		f_printf( m_pPrintfClient, " [");
  		f_printf( m_pPrintfClient, m_pRepeatingArg->getIdentifier());
  		f_printf( m_pPrintfClient, "...]");
  	}
  	
  	f_printf( m_pPrintfClient, "\n\n");
  
  	if( m_uiOptionsVecLen > 0)
  	{
  		// The following is fairly hard-coded to get the spacing right
  		
  		f_printf( m_pPrintfClient,
  			"OPTIONS:\n"
  			"\n"
  			"identifier         case sensitive   description\n"
  			"-----------------  --------------   ---------------------------------------\n");
  
  		// Show all the options
  		
  		for( uiLoop = 0; uiLoop < m_uiOptionsVecLen; uiLoop++)
  		{
  			const char *	pszHelpClone = NULL;
  			F_Arg *			pArg = (F_Arg*)m_optionsVec.getElementAt( uiLoop);
  			const char *	pszIdentifier = pArg->getIdentifier();
  			FLMUINT			uiIdentifierLength = f_strlen( pszIdentifier);
  			
  			f_printf( m_pPrintfClient, "-");
  			f_printf( m_pPrintfClient, pszIdentifier);
  			
  			if( pArg->getContentType() != F_ARG_CONTENT_NONE)
  			{
  				const char * 	pszArgArgIndicator = "=ARG";
  				
  				f_printf( m_pPrintfClient, pszArgArgIndicator);
  				uiIdentifierLength += f_strlen( pszArgArgIndicator);
  			}
  			
  			m_pPrintfClient->outputChar( ' ', 16 - uiIdentifierLength);
  			
  			f_printf( m_pPrintfClient, "  ");
  			f_printf( m_pPrintfClient, (pArg->getCaseSensitive())
  																		? "y"
  																		: "n");
  			m_pPrintfClient->outputChar( ' ', 16);
  			
  			if( RC_BAD( rc = f_strdup( pArg->getShortHelp(), 
  				(char **)&pszHelpClone)))
  			{
  				goto Exit;
  			}
  			
  			rc = displayShortHelpLines( pszHelpClone, 39);
  			
  			if( pszHelpClone)
  			{
  				f_free( &pszHelpClone);
  				pszHelpClone = NULL;
  			}
  			
  			if( RC_BAD( rc))
  			{
  				goto Exit;
  			}
  			
  			f_printf( m_pPrintfClient, "\n");
  		}
  		
  		f_printf( m_pPrintfClient, "\n");
  	}
  
  	if( m_uiRequiredArgsVecLen > 0)
  	{
  		f_printf( m_pPrintfClient,
  			"REQUIRED args:\n"
  			"\n");
  		
  		for( uiLoop = 0; uiLoop < m_uiRequiredArgsVecLen; uiLoop++)
  		{
  			F_Arg *			pArg = (F_Arg*)m_requiredArgsVec.getElementAt( uiLoop);
  			F_StringAcc 	tempAcc;
  			FLMUINT 			uiPads;
  			
  			if( RC_BAD( rc = tempAcc.appendTEXT( "<")))
  			{
  				goto Exit;
  			}
  			
  			if( RC_BAD( rc = tempAcc.appendTEXT( pArg->getIdentifier())))
  			{
  				goto Exit;
  			}
  			
  			if( RC_BAD( rc = tempAcc.appendTEXT( ">: ")))
  			{
  				goto Exit;
  			}
  			
  			uiPads = 36 - f_strlen( tempAcc.getTEXT());
  			
  			if( RC_BAD( rc = tempAcc.appendCHAR( ' ', uiPads)))
  			{
  				goto Exit;
  			}
  			
  			f_printf( m_pPrintfClient, tempAcc.getTEXT());
  			
  			if( RC_BAD( rc = displayShortHelpLines( pArg->getShortHelp(), 39)))
  			{
  				goto Exit;
  			}
  			
  			f_printf( m_pPrintfClient, "\n");
  		}
  		
  		f_printf( m_pPrintfClient, "\n\n");
  	}
  
  	if( m_uiOptionalArgsVecLen > 0)
  	{
  		f_printf( m_pPrintfClient, 
  			"OPTIONAL args:\n"
  			"\n");
  		
  		for( uiLoop = 0; uiLoop < m_uiOptionalArgsVecLen; uiLoop++)
  		{
  			F_Arg *			pArg = (F_Arg*)m_optionalArgsVec.getElementAt( uiLoop);
  			F_StringAcc 	tempAcc;
  			FLMUINT 			uiPads;
  			
  			if( RC_BAD( rc = tempAcc.appendTEXT( "[")))
  			{
  				goto Exit;
  			}
  			
  			if( RC_BAD( rc = tempAcc.appendTEXT( pArg->getIdentifier())))
  			{
  				goto Exit;
  			}
  			
  			if( RC_BAD( rc = tempAcc.appendTEXT( "]: ")))
  			{
  				goto Exit;
  			}
  			
  			uiPads = 36 - f_strlen( tempAcc.getTEXT());
  			
  			if( RC_BAD( rc = tempAcc.appendCHAR( ' ', uiPads)))
  			{
  				goto Exit;
  			}
  			
  			f_printf( m_pPrintfClient, tempAcc.getTEXT());
  			
  			if( RC_BAD( rc = displayShortHelpLines( pArg->getShortHelp(), 39)))
  			{
  				goto Exit;
  			}
  			
  			f_printf( m_pPrintfClient, "\n");
  		}
  		
  		f_printf( m_pPrintfClient, "\n\n");
  	}
  
  	if( m_pRepeatingArg)
  	{
  		F_StringAcc		tempAcc;
  		FLMUINT			uiPads;
  		
  		f_printf( m_pPrintfClient,
  			"REPEATING arg:\n"
  			"\n");
  		
  		if( RC_BAD( rc = tempAcc.appendTEXT( "[")))
  		{
  			goto Exit;
  		}
  		
  		if( RC_BAD( rc = tempAcc.appendTEXT( m_pRepeatingArg->getIdentifier())))
  		{
  			goto Exit;
  		}
  		
  		if( RC_BAD( rc = tempAcc.appendTEXT( "...]: ")))
  		{
  			goto Exit;
  		}
  		
  		uiPads = 36 - f_strlen( tempAcc.getTEXT());
  		
  		if( RC_BAD( rc = tempAcc.appendCHAR( ' ', uiPads)))
  		{
  			goto Exit;
  		}
  		
  		f_printf( m_pPrintfClient, tempAcc.getTEXT());
  		
  		if( RC_BAD( rc = displayShortHelpLines(
  			m_pRepeatingArg->getShortHelp(), 39)))
  		{
  			goto Exit;
  		}
  		
  		f_printf( m_pPrintfClient, "\n\n");
  	}
  	
  Exit:
  
  	return;
  }
  
  /****************************************************************************
  Desc: Does the given arg begin with an '@'?
  ****************************************************************************/
  FSTATIC FLMBOOL matchesAtParams( 
  	const char *		pszStr)
  {
  	f_assert( pszStr);
  	f_assert( *pszStr);
  	
  	return( (pszStr[ 0] == '@') ? TRUE : FALSE);
  }
  
  /****************************************************************************
  Desc: Does the given arg match an "=@filename" type argument
  ****************************************************************************/
  FSTATIC FLMBOOL matchesEqualsAtParams( 
  	const char *		pszStr)
  {
  	const char *		pszStartPos;
  	
  	f_assert( pszStr);
  	f_assert( *pszStr);
  
  	pszStartPos = f_strstr( pszStr, "=@");
  	
  	if( !pszStartPos)
  	{
  		return( FALSE);
  	}
  	else
  	{
  		// Should have something after the @
  		
  		if( (f_strlen( pszStartPos) > 2) && pszStartPos[ 2])
  		{
  			return( TRUE);
  		}
  		else
  		{
  			return( FALSE);
  		}
  	}
  }
  
  /****************************************************************************
  Desc: Are we done preprocessing the arg set or not for all the @'s?
  ****************************************************************************/
  FLMBOOL F_ArgSet::needsPreprocessing( void)
  {
  	FLMUINT		uiLoop;
  	
  	for( uiLoop = 0; uiLoop < m_uiArgc; uiLoop++)
  	{
  		if( matchesAtParams( (const char *)(m_pArgv->getElementAt( uiLoop))) ||
  			 matchesEqualsAtParams( (const char *)(m_pArgv->getElementAt( uiLoop))))
  		{
  			return( TRUE);
  		}
  	}
  	
  	return( FALSE);
  }
  
  /****************************************************************************
  Desc: Replace all @params.txt with whitespace-collapsed inserted arguments
  ****************************************************************************/
  RCODE F_ArgSet::processAtParams(
  	FLMUINT			uiInsertionPoint,
  	char *			pszBuffer)
  {
  	RCODE				rc = NE_FLM_OK;
  	F_Vector *		pNewVec = NULL;
  	FLMUINT			uiLoop;
  	FLMUINT			uiOldVecIndex;
  	FLMUINT			uiNewVecIndex = uiInsertionPoint;
  	FLMUINT			uiLeftToDo;
  
  	// Set up a new vector and start copying/expanding things over
  	
  	pNewVec = f_new F_Vector;
  	if( !pNewVec)
  	{
  		rc = RC_SET( NE_FLM_MEM);
  		goto Exit;
  	}
  
  	// Copy over the old items up to the insertion point
  	
  	for( uiOldVecIndex = 0; uiOldVecIndex < uiInsertionPoint; uiOldVecIndex++)
  	{
  		const char *	pszOldArg = (const char *)m_pArgv->getElementAt( uiOldVecIndex);
  		char *			pszCopy;
  		
  		if( RC_BAD( rc = f_strdup( pszOldArg, &pszCopy)))
  		{
  			goto Exit;
  		}
  		
  		if( RC_BAD( rc = pNewVec->setElementAt( pszCopy, uiOldVecIndex)))
  		{
  			f_free( &pszCopy);
  			goto Exit;
  		}
  	}
  
  	// Handle empty file case
  	
  	if( pszBuffer)
  	{
  		char *	pszStartPos;
  		char *	pszEndPos;
  		
  		// Advance pszBuffer to first non-whitespace string
  		
  		while( f_isWhitespace( *pszBuffer))
  		{
  			pszBuffer++;
  		}
  		
  		pszStartPos = pszEndPos = pszBuffer;
  		
  		while( *pszStartPos)
  		{
  			if( f_isWhitespace( *pszEndPos) || (pszEndPos[ 0] == 0))
  			{
  				FLMBYTE		ucEndChar = pszEndPos[ 0];
  				char * 		pszCopy;
  				
  				*pszEndPos = 0;
  				if( RC_BAD( rc = f_strdup( pszStartPos, &pszCopy)))
  				{
  					goto Exit;
  				}
  				
  				if( RC_BAD( rc = pNewVec->setElementAt(
  					pszCopy, uiNewVecIndex)))
  				{
  					f_free( &pszCopy);
  					goto Exit;
  				}
  				
  				uiNewVecIndex++;
  
  				// If not at end of file, advance through whitespace
  				
  				if( ucEndChar != 0)
  				{
  					while( f_isWhitespace( *(++pszEndPos)))
  					{
  						; // Just advance it to next non-whitespace
  					}
  				}
  				
  				pszStartPos = pszEndPos;
  			}
  			else
  			{
  				pszEndPos++;
  			}
  		}
  	}
  
  	// Copy the remaining args to the new vector
  	
  	uiLeftToDo = m_uiArgc - (uiInsertionPoint+1);
  	
  	for( uiLoop = 0; uiLoop < uiLeftToDo; uiLoop++)
  	{
  		char *		pszCopy;
  		
  		if( RC_BAD( rc = f_strdup( (const char *)
  			(m_pArgv->getElementAt( uiInsertionPoint + uiLoop + 1)), &pszCopy)))
  		{
  			goto Exit;
  		}
  		
  		if( RC_BAD( rc = pNewVec->setElementAt( pszCopy, uiNewVecIndex)))
  		{
  			f_free( &pszCopy);
  			goto Exit;
  		}
  		
  		uiNewVecIndex++;
  	}
  
  	// Switch over member variables to the new values
  	
  	for( uiLoop = 0; uiLoop < m_uiArgc; uiLoop++)
  	{
  		char * 	pszFreeMe = (char *)(m_pArgv->getElementAt( uiLoop));
  		
  		if( pszFreeMe)
  		{
  			f_free( &pszFreeMe);
  		}
  	}
  	
  	m_pArgv->Release();
  	m_pArgv = pNewVec;
  	m_uiArgc = uiNewVecIndex;
  	
  Exit:
  
  	if( RC_BAD( rc))
  	{
  		FLMUINT		uiClean;
  		
  		for( uiClean = 0; uiClean < uiNewVecIndex; uiClean++)
  		{
  			char * 	pszStr = (char *)pNewVec->getElementAt( uiClean);
  			
  			if( pszStr)
  			{
  				f_free( &pszStr);
  			}
  		}
  		
  		if( pNewVec)
  		{
  			pNewVec->Release();
  		}
  	}
  	
  	return( rc);
  }
  
  /****************************************************************************
  Desc: Preprocess all @-style arguments
  ****************************************************************************/
  RCODE F_ArgSet::preProcessParams( void)
  {
  	RCODE					rc = NE_FLM_OK;
  	char *				pszBuffer = NULL;
  	FLMUINT				uiLoop = 0;
  	const FLMUINT		uiMaxPreProcessingCycles = 64;
  	FLMUINT				uiPreProcessingCycles = 0;
  
  	for( uiLoop = 0; uiLoop < m_uiArgc;)
  	{
  		const char * pszNextArg = (const char *)m_pArgv->getElementAt( uiLoop);
  		
  		if( matchesAtParams( pszNextArg))
  		{
  			uiPreProcessingCycles++;
  			
  			if( uiPreProcessingCycles >= uiMaxPreProcessingCycles)
  			{
  				// There's probably a cycle in the @-files if we're going this deep
  				
  				f_printf( m_pPrintfClient, "ERROR: Cycle in @-files detected");
  				rc = RC_SET( NE_FLM_FAILURE);
  				goto Exit;
  			}
  			
  			// pszNextArg has the form @params.txt
  			
  			if( RC_BAD( rc = f_filetobuf(
  				(const char *)(pszNextArg + 1), &pszBuffer)))
  			{
  				f_printf( m_pPrintfClient, "ERROR: Reading @-file ");
  				f_printf( m_pPrintfClient, pszNextArg);
  				f_printf( m_pPrintfClient, "!");
  				goto Exit;
  			}
  			
  			// f_filetobuf just allocated a pszBuffer with the
  			// file contents
  			
  			rc = processAtParams( uiLoop, pszBuffer);
  			
  			if( pszBuffer)
  			{
  				f_free( &pszBuffer);
  			}
  			
  			if( RC_BAD( rc))
  			{
  				goto Exit;
  			}
  		}
  		else
  		{
  			uiLoop++;
  		}
  	}
  	
  	// Now do the -arg=@params style, once each
  	
  	for( uiLoop = 0; uiLoop < m_uiArgc; uiLoop++)
  	{
  		const char * pszNextArg = (const char *)m_pArgv->getElementAt( uiLoop);
  		
  		if( matchesEqualsAtParams( pszNextArg))
  		{
  			char *		pszFile = f_strstr( pszNextArg, "=@");
  			char *		pszFinalBuffer;
  
  			pszFile++;
  			pszFile[ 0] = 0;
  			pszFile++;
  			
  			if( RC_BAD( rc = f_filetobuf( (const char *)pszFile, &pszBuffer)))
  			{
  				goto Exit;
  			}
  
  			if( RC_BAD( rc = f_alloc( f_strlen( pszNextArg) + 
  					f_strlen( pszBuffer) + 1, &pszFinalBuffer)))
  			{
  				f_free( &pszBuffer);
  				goto Exit;
  			}
  
  			// Replace existing -arg=@params.txt string
  			// with one of the form -arg=1 2 3 4 5, etc.
  			
  			f_strcpy( pszFinalBuffer, pszNextArg);
  			f_strcat( pszFinalBuffer, pszBuffer);
  			f_free( &pszBuffer);
  			
  			const char *	pszOldStr = (const char *)m_pArgv->getElementAt( uiLoop);
  			
  			if( pszOldStr)
  			{
  				f_free( &pszOldStr);
  			}
  			
  			if( RC_BAD( rc = m_pArgv->setElementAt( pszFinalBuffer, uiLoop)))
  			{
  				f_free( &pszFinalBuffer);
  				goto Exit;
  			}
  			
  			break;
  		}
  	}
  	
  Exit:
  
  	return( rc);
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMBOOL FTKAPI F_ArgSet::argIsPresent( 
  	const char *				pszIdentifier)
  {
  	return( getArg( pszIdentifier)->isPresent());
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMUINT FTKAPI F_ArgSet::getValueCount( 
  	const char *				pszIdentifier)
  {
  	return( getArg( pszIdentifier)->getValueCount());
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMUINT FTKAPI F_ArgSet::getUINT( 
  	const char *				pszIdentifier,
  	FLMUINT						uiIndex)
  {
  	return( getArg( pszIdentifier)->getUINT( uiIndex));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMINT FTKAPI F_ArgSet::getINT( 
  	const char *				pszIdentifier,
  	FLMUINT						uiIndex)
  {
  	return( getArg( pszIdentifier)->getINT( uiIndex));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  FLMBOOL FTKAPI F_ArgSet::getBOOL(
  	const char *				pszIdentifier,
  	FLMUINT						uiIndex)
  {
  	return( getArg( pszIdentifier)->getBOOL( uiIndex));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  const char * FTKAPI F_ArgSet::getString(
  	const char *			pszIdentifier,
  	FLMUINT					uiIndex)
  {
  	return( getArg( pszIdentifier)->getString( uiIndex));
  }
  
  /****************************************************************************
  Desc:
  ****************************************************************************/
  void FTKAPI F_ArgSet::getString( 
  	const char *			pszIdentifier,
  	char *					pszDestination,
  	FLMUINT					uiDestinationBufferSize,
  	FLMUINT					uiIndex)
  {
  	getArg( pszIdentifier)->getString( pszDestination,
  		uiDestinationBufferSize, uiIndex);
  }
  
  