/*****************************************************************************/
/* SOURCE FILE */
/*****************************************************************************/
/*
$Archive: $
$Revision: $
$Date: $
$Author: $
Description: Class Implementation : CmdLineOptions
TOOL And XML FORMS License
==========================
Except where otherwise noted, all of the documentation
and software included in the TOOL package is
copyrighted by Michael Swartzendruber.
Copyright (C) 2005 Michael John Swartzendruber.
All rights reserved.
Access to this code, whether intentional or accidental,
does NOT IMPLY any transfer of rights.
This software is provided "as-is," without any express
or implied warranty. In no event shall the author be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for
any purpose, including commercial applications, and to
alter and redistribute it, provided that the following
conditions are met:
1. All redistributions of source code files must retain
all copyright notices that are currently in place,
and this list of conditions without modification.
2. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
3. If you use this software in another product, an acknowledgment
in the product documentation would be appreciated but is
not required.
4. Modified versions in source or binary form must be plainly
marked as such, and must not be misrepresented as being
the original software.
*/
static char OBJECT_ID[] = "$Revision: $ : $Date: $";
/*****************************************************************************/
#pragma warning( disable : 4786 ) // turn off goofy little warnings about
#pragma warning( disable : 4503 ) // names too long from the stl
#include "../stdafx.h"
#include <assert.h>
#include <iterator>
#include <string>
#include <map>
#include <vector>
#include "CmdLineOptions.h"
#include "CmdLineException.h"
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::CmdLineOptions
DESCRIPTION: ctor
INPUT:
RETURNS:
*/
CmdLineOptions::CmdLineOptions()
{
}
/* end of function "CmdLineOptions::CmdLineOptions" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::~CmdLineOptions
DESCRIPTION: dtor
INPUT:
RETURNS:
*/
CmdLineOptions::~CmdLineOptions()
{
m_oCmdOptionsList.erase( m_oCmdOptionsList.begin(), m_oCmdOptionsList.end() );
ResetMaps();
}
/* end of function "CmdLineOptions::~CmdLineOptions" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::ResetMaps
DESCRIPTION: Reset contents of all maps and lists. Called by destructor
INPUT:
RETURNS:
*/
void CmdLineOptions::ResetMaps( void )
{
ENTRY_VALUES::iterator oEntry;
for ( oEntry = m_oUserEntries.begin();
oEntry != m_oUserEntries.end();
oEntry++ )
{
CmdLineEntry* oContainer = (*oEntry).second;
if ( CmdLineEntry::eList == oContainer->GetContainerType() )
if ( !oContainer->m_oList.empty() )
oContainer->m_oList.erase( oContainer->m_oList.begin(),
oContainer->m_oList.end() );
else
if ( !oContainer->m_oMap.empty() )
oContainer->m_oMap.erase( oContainer->m_oMap.begin(),
oContainer->m_oMap.end() );
delete oContainer;
}
m_oUserEntries.erase( m_oUserEntries.begin(), m_oUserEntries.end() );
m_oAbbrMap.erase( m_oAbbrMap.begin(), m_oAbbrMap.end() );
}
/* end of function "CmdLineOptions::ResetMaps" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::Add
DESCRIPTION: Add the Option Description to the array. You need not
worry about Option descriptions going out of scope because
they are copied internally before being added to an option
description array
INPUT: poOptionDescription : An Option Description
RETURNS:
*/
void CmdLineOptions::Add( CmdLineArg* poOptionDescription )
{
bool bInserted = FALSE;
for ( unsigned int i = 0; i < m_oCmdOptionsList.size(); i++ ) // 2b|!2b==?
{
std::string oArrayElement = m_oCmdOptionsList[ i ].GetOptName();
std::string oNewElement = poOptionDescription->GetOptName();
// Insert with longest GetOptName's first
//
if ( oNewElement.length() >= oArrayElement.length() )
{
bInserted = TRUE;
ARG_VECTOR::iterator oInsertPosition = m_oCmdOptionsList.begin();
for ( unsigned int iLoop = 0; iLoop <= i; iLoop++ ) // 2b|!2b==?
oInsertPosition++;
m_oCmdOptionsList.insert( oInsertPosition, *poOptionDescription );
break;
}
}
if ( !bInserted )
m_oCmdOptionsList.insert( m_oCmdOptionsList.end(), *poOptionDescription );
}
/* end of function "CmdLineOptions::Add" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::Add
DESCRIPTION: Add the Option Description to the array
INPUT: psczOptionName : the name of the option, CASE-INSENSITIVE
iFlags : the flags of the option
CMDLINE_ARGOK Does option take an argument
CMDLINE_REPEATOK Is option valid more than once?
CMDLINE_REP_FIRST Use first if option is repeated
CMDLINE_REP_LAST Use last if option is repeated
(i.e. /l dir1 /l dir2)
CMDLINE_REP_ARRAY Build array if option is repeated
CMDLINE_ASSOCIATIVE Takes two arguments
i.e. /D variable=value or /D variable value
CMDLINE_FIRSTARG Takes an argument, is repeatable,
and repeats are ignored
CMDLINE_LASTARG Takes an argument, is repeatable,
and repeats are ignored
CMDLINE_ARRAY Takes an argument, is repeatable, builds an array
(i.e. The INCLUDE option would use this)
CMDLINE_ASSOCARRAY Takes two arguments, is repeatable, builds
associative array
psczDescription : the description of the option, for usage messages
psczAbbr : the abbreviation of the option.
A DEFINITE abbreviation of the argument, this abbreviation will
be used unless it conflicts with another option's DEFINITE
abbreviation.
This can be used to have an abbreviation of I for the INCLUDE
option, even if there is another option which begins with I.
For all options, any UNIQUE shortening of the option name is
acceptable.
For example, with an option name of INCLUDE, possible
abbreviations would be I, IN, INC, INCL, INCLU, INCLUD.
Which of these possibilities are valid, depends on which ones
are are unique for a given option set.
The DEFINITE abbreviation need not be a true abbreviation of
the option name.
For example, an option name EXPORT, could have a DEFINITE
abbreviation of X even though that is not strictly an
abbreviation of the name.
RETURNS:
*/
void CmdLineOptions::Add( const char* pszcOptionName,
const int iFlags,
const char* pszcDescription,
const char* pszcAbbr )
{
CmdLineArg oToAdd( pszcOptionName, iFlags, pszcDescription, pszcAbbr );
Add( &oToAdd );
}
/* end of function "CmdLineOptions::Add" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::Remove
DESCRIPTION: Removes this option from all internal lists
INPUT:
RETURNS: TRUE is item found/removed FALSE otherwise
*/
bool CmdLineOptions::Remove( const char* pszcOption )
{
std::string oOption = pszcOption;
for ( std::string::iterator iter = oOption.begin();
iter != oOption.end();
iter++ )
(*iter) = toupper( *iter );
ENTRY_VALUES::iterator oDeleteLocation = m_oUserEntries.find( oOption );
if ( oDeleteLocation != m_oUserEntries.end() )
{
CmdLineEntry* oContainer = (*oDeleteLocation).second;
if ( CmdLineEntry::eList == oContainer->GetContainerType() )
if ( !oContainer->m_oList.empty() )
oContainer->m_oList.erase( oContainer->m_oList.begin(),
oContainer->m_oList.end() );
else
if ( !oContainer->m_oMap.empty() )
oContainer->m_oMap.erase( oContainer->m_oMap.begin(),
oContainer->m_oMap.end() );
delete oContainer;
m_oUserEntries.erase( oDeleteLocation );
}
ABBR_MAP::iterator oDelLocation = m_oAbbrMap.find( oOption );
if ( oDelLocation != m_oAbbrMap.end() )
m_oAbbrMap.erase( oDelLocation );
bool bFound = FALSE;
for ( unsigned int i = 0; i < m_oCmdOptionsList.size(); i++ ) // 2b|!2b==?
{
CmdLineArg oArg = m_oCmdOptionsList[ i ];
if ( oArg.GetOptName() == oOption )
{
ARG_VECTOR::iterator oDeleteLocation = m_oCmdOptionsList.begin();
for ( unsigned int k = 0; k <= i; k++ ) // 2b|!2b==?
oDeleteLocation++;
m_oCmdOptionsList.erase( oDeleteLocation );
bFound = TRUE;
break;
}
}
return bFound;
}
/* end of function "CmdLineOptions::Remove" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::BuildAbbrTable
DESCRIPTION: walk through CmdOptDescList, gathering .GetOptName()
abbreviations. Use this info to construct an abbreviation
map
INPUT:
RETURNS:
*/
void CmdLineOptions::BuildAbbrTable( void )
{
STRING_ARRAY oAbbrevArray;
std::vector<unsigned int> oIndexArray;
STRING_ARRAY oClashArray;
// Initialize oAbbrevArray with complete option names
// (and oIndexArray with pointers). This array is size
// sorted because CmdOptDescList is size sorted
//
for ( unsigned int i = 0; i < m_oCmdOptionsList.size(); i++ )
{
oAbbrevArray.insert( oAbbrevArray.end(), m_oCmdOptionsList[i].GetOptName() );
oIndexArray.insert( oIndexArray.end(), i );
std::string oReqAbbr = m_oCmdOptionsList[i].GetOptAbbr();
if ( oReqAbbr.length() )
{
oAbbrevArray.insert( oAbbrevArray.end(), oReqAbbr );
oIndexArray.insert( oIndexArray.end(), i );
}
}
// Now walk through AbbrMap extending it as we go with abbreviations
//
unsigned int iAbbr; // 2b|!2b==?
for ( iAbbr = 0;iAbbr < oAbbrevArray.size(); iAbbr++ )
{
std::string sAbbr = oAbbrevArray[ iAbbr ];
// Abbr is the name already in the map
//
int iNewAbbrLength = sAbbr.length() - 1;
if ( iNewAbbrLength )
{
// No zero length abbreviations
// NewAbbr is the name we are trying to add to map
//
std::string sNewAbbr = sAbbr.substr( 0, iNewAbbrLength );
// See if sNewAbbr is unique
// (ignoring all the entries we've already processed)
//
bool bUnique = TRUE;
unsigned int iCmp; // 2b|!2b==?
for ( iCmp = iAbbr + 1; iCmp < oAbbrevArray.size(); iCmp++ )
{
// Is sNewAbbr an abbreviation of sCmp?
// Assumes that Left() returns all characters (without
// complaint) in the case that Left() is passed a number
// larger than the string length.
//
std::string sCmp = oAbbrevArray[ iCmp ].substr( 0, sNewAbbr.length() );
if ( sCmp == sNewAbbr )
{
// sNewAbbr is not unique ... abandon it
//
bUnique = FALSE;
break;
}
}
// Did we previously find a clash that's a substring of sNewAbbr
//
if ( bUnique )
{
for ( unsigned int iClash = 0; iClash < oClashArray.size(); iClash++ ) // 2b|!2b==?
{
std::string sCmp = oClashArray[ iClash ];
if ( sCmp == sNewAbbr )
{
bUnique = FALSE;
break;
}
}
}
if ( bUnique )
{
oAbbrevArray.insert( oAbbrevArray.end(), sNewAbbr );
oIndexArray.insert( oIndexArray.end(), oIndexArray[ iAbbr ] );
}
else
{
oClashArray.insert( oClashArray.end(), sNewAbbr );
}
}
}
assert( oAbbrevArray.size() == oIndexArray.size() );
// Hmmm, this will freak if user calls this twice ... handle that case -tab
//
assert( m_oAbbrMap.size() == 0 );
// Now Build AbbrMap from oAbbrevArray and oIndexArray
//
for ( unsigned int j = 0; j < oAbbrevArray.size(); j++ ) // 2b|!2b==?
{
std::string oKey = oAbbrevArray[ j ];
unsigned int iIndex = oIndexArray[ j ];
CmdLineArg oArg = m_oCmdOptionsList[ iIndex ];
m_oAbbrMap.insert( ABBR_MAP::value_type( oKey, oArg ) );
}
}
/* end of function "CmdLineOptions::BuildAbbrTable" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::ParseCmdLine
DESCRIPTION: Construct array of actual parameters based on command
line object. Can throw an CCommandLineException
INPUT: oCmdLine : the Command line object to parse
pRestList : when provided will contain the strings on the
command line that aren't flags. Is actually
the same as the return string except that the
separate parts are stored in a Stringlist.
RETURNS: Anything left over on the command line after parsing
the valid options
*/
std::string CmdLineOptions::ParseCmdLine( CmdLine& oCmdLine,
STRING_ARRAY* pRestList )
{
std::string oReturn;
// Clear all previous maps and rebuild afterwards
//
ResetMaps();
BuildAbbrTable();
int iNumArgs = oCmdLine.GetNumArgs();
for ( int iArg = 0; iArg <= iNumArgs; iArg++ )
{
std::string oToken = oCmdLine[ iArg ];
if ( ( '-' == oToken[ 0 ] ) || ( '/' == oToken[ 0 ] ) )
oToken = oToken.substr( 1, oToken.length() );
for ( std::string::iterator iter = oToken.begin();
iter != oToken.end();
iter++ )
(*iter) = toupper( *iter );
ABBR_MAP::iterator oMapLocation = m_oAbbrMap.find( oToken );
if ( oMapLocation != m_oAbbrMap.end() )
{
// Valid switch or abbreviation
//
CmdLineArg oTemp = m_oAbbrMap[ oToken ];
ENTRY_VALUES::iterator oLocation = m_oUserEntries.find( oTemp.GetOptName() );
bool bIsAlreadyThere;
if ( oLocation != m_oUserEntries.end() )
bIsAlreadyThere = true;
else
bIsAlreadyThere = false;
if ( bIsAlreadyThere && ( !oTemp.IsRepeatable() ) )
{
char achMsg[ 80 ];
sprintf( achMsg,
"%s can only be specified once\r\n",
oToken.c_str() );
throw( new CmdLineException( achMsg, this ) );
}
else
if ( bIsAlreadyThere && oTemp.ReturnsFirstRep() )
{
// It's already there ... do nothing
}
else
{
// no entry found for this argument yet, so make a new container
// for this argument
//
if ( !bIsAlreadyThere )
{
if ( oTemp.ReturnsAssociative() )
m_oUserEntries.insert( ENTRY_VALUES::value_type( oTemp.GetOptName(),
new CmdLineEntry( CmdLineEntry::eMap ) ) );
else
m_oUserEntries.insert( ENTRY_VALUES::value_type( oTemp.GetOptName(),
new CmdLineEntry( CmdLineEntry::eList ) ) );
}
if ( oTemp.TakesArg() && !oTemp.ReturnsArray() && !oTemp.ReturnsAssociative() )
{
// grab the next command line argument and stash it into to the list
// with the current key
//
ENTRY_VALUES::iterator oLocation = m_oUserEntries.find( oTemp.GetOptName() );
CmdLineEntry* oContainer = (*oLocation).second;
assert( oContainer->GetContainerType() == CmdLineEntry::eList );
if ( !oContainer->m_oList.empty() )
oContainer->m_oList.erase( oContainer->m_oList.begin(),
oContainer->m_oList.end() );
oContainer->m_oList.insert( oContainer->m_oList.end(), oCmdLine[ ++iArg ] );
}
else
if ( oTemp.ReturnsAssociative() )
{
if ( iArg + 2 > iNumArgs )
{
char achMsg[80];
sprintf( achMsg,
"%s requires 2 arguments\r\n",
oTemp.GetOptName() );
throw( new CmdLineException( achMsg, this ) );
}
ENTRY_VALUES::iterator oLocation = m_oUserEntries.find( oTemp.GetOptName() );
if ( oLocation == m_oUserEntries.end() )
{
m_oUserEntries.insert( ENTRY_VALUES::value_type( oTemp.GetOptName(),
new CmdLineEntry( CmdLineEntry::eMap ) ) );
}
assert( (*oLocation).second->GetContainerType() == CmdLineEntry::eMap );
(*oLocation).second->m_oMap.insert( STRING_MAP_TO_STRING::value_type( oCmdLine[ ++iArg ],
oCmdLine[ ++iArg ] ) );
}
else
if ( oTemp.ReturnsArray() )
{
// in this case, either create a key/value pair --or--
// append the new argument on to the existing key value
// pair
//
if ( iArg + 1 > iNumArgs /*-1*/ )
{
char achMsg[ 80 ];
sprintf( achMsg,
"%s requires argument\r\n",
oTemp.GetOptName() );
throw( new CmdLineException( achMsg, this ) );
}
ENTRY_VALUES::iterator oLocation = m_oUserEntries.find( oTemp.GetOptName() );
CmdLineEntry* oContainer = (*oLocation).second;
assert( oContainer->GetContainerType() == CmdLineEntry::eList );
oContainer->m_oList.insert( oContainer->m_oList.end(), oCmdLine[ ++iArg] );
}
}
}
else
{
if ( pRestList != NULL )
{
pRestList->insert( pRestList->end(), oCmdLine[ iArg ] );
}
oReturn += oCmdLine[ iArg ];
oReturn += " ";
}
}
// Glue the remaining strings back together
//
while ( iArg < oCmdLine.GetNumArgs() )
{
oReturn += oCmdLine[ iArg++ ];
oReturn += " ";
}
return oReturn;
}
/* end of function "CmdLineOptions::ParseCmdLine" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::GetOptionObject
DESCRIPTION: Finds/returns the Option descriptor for the given command
line option
INPUT: psczOption : the name of the option
RETURNS: the option object with pszcOption as NAME. NULL if not found.
*/
const CmdLineArg* CmdLineOptions::GetOptionObject( const char* psczOption ) const
{
std::string oOption( psczOption );
for ( std::string::iterator iter = oOption.begin();
iter != oOption.end();
iter++ )
(*iter) = toupper( *iter );
bool bFound( FALSE );
CmdLineArg oArg;
for ( unsigned int i = 0; i < m_oCmdOptionsList.size(); i++ ) // 2b|!2b==?
{
oArg = m_oCmdOptionsList[ i ];
if ( oArg.GetOptName() == oOption )
{
bFound = TRUE;
break;
}
}
return bFound ? &oArg : NULL;
}
/* end of function "CmdLineOptions::GetOptionObject" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::IsEnabled
DESCRIPTION: works for any of the argument forms. Determines whether
or not the command line option was entered.
INPUT: pszcOption : the option to look for
RETURNS: Did the option appear on the command line or not
*/
bool CmdLineOptions::IsEnabled( const char* pszcOption ) const
{
std::string oOption = pszcOption;
for ( std::string::iterator iter = oOption.begin();
iter != oOption.end();
iter++ )
(*iter) = toupper( *iter );
return ( m_oUserEntries.end() != m_oUserEntries.find( oOption ) );
}
/* end of function "CmdLineOptions::IsEnabled" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::GetValue
DESCRIPTION: Can be used with single-occurrence, single-argument options
INPUT: pszcOption: the option to look up the values for
RETURNS: The argument associated with the option
*/
std::string CmdLineOptions::GetValue( const char* pszcOption ) const
{
std::string oOption = pszcOption;
for ( std::string::iterator iter = oOption.begin();
iter != oOption.end();
iter++ )
(*iter) = toupper( *iter );
CmdLineArg oArgument;
ABBR_MAP::const_iterator oMapLocation = m_oAbbrMap.find( oOption );
if ( m_oAbbrMap.end() != oMapLocation )
{
oArgument = (*oMapLocation).second;
assert( !oArgument.ReturnsArray() && !oArgument.ReturnsAssociative() );
ENTRY_VALUES::const_iterator oEntries = m_oUserEntries.find( oOption );
if ( m_oUserEntries.end() != oEntries )
{
CmdLineEntry* oContainer = (*oEntries).second;
assert( oContainer->GetContainerType() == CmdLineEntry::eList );
return( oContainer->m_oList[0] );
}
else
return "";
}
else
return "";
}
/* end of function "CmdLineOptions::GetValue" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::GetValues
DESCRIPTION: Can be used with single-argument, multiple-occurrence
options
INPUT: pszcOption: the name of the option to get the values for
RETURNS: The list of arguments associated with the option
*/
const STRING_ARRAY* CmdLineOptions::GetValues( const char* pszcOption ) const
{
std::string oOption = pszcOption;
for ( std::string::iterator iter = oOption.begin();
iter != oOption.end();
iter++ )
(*iter) = toupper( *iter );
CmdLineArg oArgument;
ABBR_MAP::const_iterator oMapLocation = m_oAbbrMap.find( oOption );
if ( m_oAbbrMap.end() != oMapLocation )
{
oArgument = (*oMapLocation).second;
assert( oArgument.ReturnsArray() );
ENTRY_VALUES::const_iterator oEntries = m_oUserEntries.find( oOption );
if ( m_oUserEntries.end() != oEntries )
{
CmdLineEntry* oContainer = (*oEntries).second;
assert( oContainer->GetContainerType() == CmdLineEntry::eList );
return( &( oContainer->m_oList ) );
}
else
return NULL;
}
else
return NULL;
}
/* end of function "CmdLineOptions::GetValues" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::GetAssocPairs
DESCRIPTION: Is used with options that take two arguments, the first
being a key name, the second the value of of the key. This
is useful for options that set variables like
-Dfoo=bar
INPUT: pszcOption: the argument to get the values for
RETURNS: The list of combined arguments associated with the option
*/
const STRING_MAP_TO_STRING* CmdLineOptions::GetAssocPairs( const char* pszcOption ) const
{
std::string oOption = pszcOption;
for ( std::string::iterator iter = oOption.begin();
iter != oOption.end();
iter++ )
(*iter) = toupper( *iter );
CmdLineArg oArgument;
ABBR_MAP::const_iterator oMapLocation = m_oAbbrMap.find( oOption );
if ( m_oAbbrMap.end() != oMapLocation )
{
oArgument = (*oMapLocation).second;
assert( oArgument.ReturnsAssociative() );
ENTRY_VALUES::const_iterator oEntries = m_oUserEntries.find( oOption );
if ( m_oUserEntries.end() != oEntries )
{
CmdLineEntry* oContainer = (*oEntries).second;
assert( oContainer->GetContainerType() == CmdLineEntry::eMap );
return( &( oContainer->m_oMap ) );
}
else
{
return NULL;
}
}
else
return NULL;
}
/* end of function "CmdLineOptions::GetAssocPairs" */
/*****************************************************************************/
/*****************************************************************************/
/*
FUNCTION NAME: CmdLineOptions::Usage
DESCRIPTION: prints usage message if StdHandles are available
INPUT:
RETURNS:
*/
std::string CmdLineOptions::Usage( const char* pszcMsg ) const
{
std::string oMsg( pszcMsg );
std::string oLine;
oMsg += "\n";
for ( unsigned int i = 0; i < m_oCmdOptionsList.size(); i++ ) // 2b|!2b==?
{
CmdLineArg oArgument = m_oCmdOptionsList[ i ];
std::string oAbbr = oArgument.GetOptAbbr();
if ( oAbbr.length() )
{
std::string oTemp = "(-";
oTemp += oAbbr;
oTemp += ")";
oAbbr = oTemp;
}
oLine = oArgument.GetOptName();
oLine += "\t";
oLine += oAbbr;
oLine += "\t";
oLine += oArgument.GetDescription();
oLine += "\n";
oMsg += oLine;
}
return oMsg;
}
/* end of function "CmdLineOptions::Usage" */
/*****************************************************************************/
/*****************************************************************************/
/* Check-in history */
/*
*$Log: $
*/
/*****************************************************************************/