/*------------------------------------------------------------------------------
* Debugging Microsoft .NET 2.0 Applications
* Copyright © 1997-2006 John Robbins -- All rights reserved.
-----------------------------------------------------------------------------*/
using System;
using System.Diagnostics;
using System.Globalization;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics.CodeAnalysis;
namespace Wintellect.Paraffin
{
///
/// A command line argument parsing class.
///
///
/// This class is based on the WordCount version from the Framework SDK
/// samples. Any errors are mine.
///
/// There are two arrays of flags you'll pass to the constructors. The
/// flagSymbols are supposed to be standalone switches that toggle an option
/// on. The dataSymbols are for switches that take data values. For
/// example, if your application needs a switch, -c, to set the count,
/// you'd put "c" in the dataSymbols. Note that you can pass null/Nothing
/// for dataSymbols if you don't need them.
///
///
internal abstract class ArgParser
{
// For example: "/", "-"
private String [] switchChars;
// Switch character(s) that are simple flags
private String [] flagSymbols;
// Switch characters(s) that take parameters. For example: -f .
// This can be null if not needed.
private String [] dataSymbols;
// Are switches case-sensitive?
private Boolean caseSensitiveSwitches;
///
/// The status values for various internal methods.
///
protected enum SwitchStatus
{
///
/// Success.
///
NoError ,
///
/// There was a problem.
///
Error ,
///
/// Show the usage help.
///
ShowUsage
} ;
/////
///// Constructs the class with nothing but flag switches and defaults to
///// "/" and "-" as valid switch characters.
/////
/////
///// The array of simple flags to toggle options on or off.
/////
//protected ArgParser ( String [] flagSymbols )
// : this ( flagSymbols ,
// null ,
// false ,
// new string [] { "/" , "-" } )
//{
//}
/////
///// Constructs the class with nothing but flag switches and defaults to
///// "/" and "-" as valid switch characters.
/////
/////
///// The array of simple flags to toggle options on or off.
/////
/////
///// True if case sensitive switches are supposed to be used.
/////
//protected ArgParser ( String [] flagSymbols ,
// Boolean caseSensitiveSwitches )
// : this ( flagSymbols ,
// null ,
// caseSensitiveSwitches ,
// new string [] { "/" , "-" } )
//{
//}
/////
///// Constructs the class to use case-insensitive switches and
///// defaults to "/" and "-" as valid switch characters.
/////
/////
///// The array of simple flags to toggle options on or off.
/////
/////
///// The array of options that need data either in the next parameter or
///// after the switch itself. This value can be null/Nothing.
/////
//protected ArgParser ( String [] flagSymbols , String [] dataSymbols )
// : this ( flagSymbols ,
// dataSymbols ,
// false ,
// new string [] { "/" , "-" } )
//{
//}
/////
///// Constructs the class and defaults to "/" and "-" as the only
///// valid switch characters
/////
/////
///// The array of simple flags to toggle options on or off.
/////
/////
///// The array of options that need data either in the next parameter or
///// after the switch itself. This value can be null/Nothing.
/////
/////
///// True if case sensitive switches are supposed to be used.
/////
protected ArgParser ( String [] flagSymbols ,
String [] dataSymbols ,
Boolean caseSensitiveSwitches )
: this ( flagSymbols ,
dataSymbols ,
caseSensitiveSwitches ,
new string [] { "/" , "-" } )
{
}
///
/// Constructor where the caller sets all options to the class.
///
///
/// The array of simple flags to toggle options on or off.
///
///
/// The array of options that need data either in the next parameter or
/// after the switch itself. This value can be null/Nothing.
///
///
/// True if case sensitive switches are supposed to be used.
///
///
/// The array of switch characters to use.
///
///
/// Thrown if or
/// are invalid.
///
protected ArgParser ( String [] flagSymbols ,
String [] dataSymbols ,
Boolean caseSensitiveSwitches ,
String [] switchChars )
{
Debug.Assert ( null != flagSymbols , "null != flagSymbols" );
// Avoid assertion side effects in debug builds.
#if DEBUG
if ( null != flagSymbols )
{
Debug.Assert ( flagSymbols.Length > 0 ,
"flagSymbols.Length > 0" );
}
#endif
if ( ( null == flagSymbols ) || ( 0 == flagSymbols.Length ) )
{
throw new ArgumentException ( Constants.ArrayMustBeValid ,
"flagSymbols" );
}
Debug.Assert ( null != switchChars , "null != switchChars" );
// Avoid assertion side effects in debug builds.
#if DEBUG
if ( null != switchChars )
{
Debug.Assert ( switchChars.Length > 0 ,
"switchChars.Length > 0" );
}
#endif
if ( ( null == switchChars ) || ( 0 == switchChars.Length ) )
{
throw new ArgumentException ( Constants.ArrayMustBeValid ,
"switchChars" );
}
this.flagSymbols = flagSymbols;
this.dataSymbols = dataSymbols;
this.caseSensitiveSwitches = caseSensitiveSwitches;
this.switchChars = switchChars;
}
///
/// Reports correct command line usage.
///
///
/// The string with the invalid command line option.
///
public abstract void OnUsage ( String errorInfo );
// Every derived class must implement an OnSwitch method or a switch is
// considered an error.
///
/// Called when a switch is parsed out.
///
///
/// The switch value parsed out.
///
///
/// The value of the switch. For flag switches this is null/Nothing.
///
///
/// One of the values.
///
protected virtual SwitchStatus OnSwitch ( String switchSymbol ,
String switchValue )
{
return ( SwitchStatus.Error );
}
///
/// Called when a non-switch value is parsed out.
///
///
/// The value parsed out.
///
///
/// One of the values.
///
protected virtual SwitchStatus OnNonSwitch ( String value )
{
return ( SwitchStatus.Error );
}
///
/// Called when parsing is finished so final sanity checking can be
/// performed.
///
///
/// One of the values.
///
protected virtual SwitchStatus OnDoneParse ( )
{
// By default, we'll assume that all parsing was an error.
return ( SwitchStatus.Error );
}
/////
///// Parses the application's command-line arguments.
/////
/////
///// True if the parsing succeeded.
/////
//public Boolean Parse ( )
//{
// // Visual Basic will use this method since its entry-point function
// // doesn't get the command-line arguments passed to it.
// return ( Parse ( Environment.GetCommandLineArgs ( ) ) );
//}
// Looks to see if the switch is in the array.
private int IsSwitchInArray ( String [] switchArray ,
String value )
{
String valueCompare = value;
if ( true == caseSensitiveSwitches )
{
valueCompare = value.ToUpperInvariant ( );
}
int retValue = -1;
for ( int n = 0 ; n < switchArray.Length ; n++ )
{
String currSwitch = switchArray [ n ];
if ( true == caseSensitiveSwitches )
{
currSwitch = currSwitch.ToUpperInvariant ( );
}
if ( 0 == String.CompareOrdinal ( valueCompare ,
currSwitch ) )
{
retValue = n;
break;
}
}
return ( retValue );
}
///
/// Looks to see if this string starts with a switch character.
///
///
/// The string to check.
///
///
/// True if the string starts with a switch character.
///
private Boolean StartsWithSwitchChar ( String value )
{
Boolean isSwitch = false;
for ( int n = 0 ; !isSwitch && ( n < switchChars.Length ) ; n++ )
{
if ( 0 == String.CompareOrdinal ( value ,
0 ,
switchChars [ n ] ,
0 ,
1 ) )
{
isSwitch = true;
break;
}
}
return ( isSwitch );
}
///
/// Parses an arbitrary set of arguments.
///
///
/// The string array to parse through.
///
///
/// True if parsing was correct.
///
///
/// Thrown if is null or empty.
///
[SuppressMessage ( "Microsoft.Globalization" ,
"CA1308:NormalizeStringsToUppercase" ,
Justification = "I NEED TO FIX THIS!" )]
public Boolean Parse ( String [] args )
{
Debug.Assert ( null != args , "null != args" );
// Avoid side effects in debug builds.
#if DEBUG
if ( null != args )
{
Debug.Assert ( args.Length > 0 , "args.Length > 0" );
}
#endif
if ( ( null == args ) || ( args.Length == 0 ) )
{
throw new ArgumentException ( Constants.InvalidParameter );
}
// Assume parsing is successful.
SwitchStatus ss = SwitchStatus.NoError;
int errorArg = -1;
int currArg;
for ( currArg = 0 ;
( ss == SwitchStatus.NoError ) && ( currArg < args.Length ) ;
currArg++ )
{
errorArg = currArg;
// Determine if this argument starts with a valid switch
// character
Boolean isSwitch = StartsWithSwitchChar ( args [ currArg ] );
if ( true == isSwitch )
{
// Indicates the symbol is a data symbol.
Boolean useDataSymbols = false;
// Get the argument itself.
String processedArg = args [ currArg ].Substring ( 1 );
// The index into the symbol array.
int n;
// First check the flags array.
n = IsSwitchInArray ( flagSymbols , processedArg );
// If it's not in the flags array, try the data array if that
// array is not null.
if ( ( -1 == n ) && ( null != dataSymbols ) )
{
n = IsSwitchInArray ( dataSymbols , processedArg );
useDataSymbols = true;
}
if ( -1 != n )
{
String theSwitch = null;
String dataValue = null;
// If it's a flag switch.
if ( false == useDataSymbols )
{
// This is a legal switch, notified the derived
// class of this switch and its value.
theSwitch = flagSymbols [ n ];
if ( caseSensitiveSwitches )
{
theSwitch = flagSymbols [ n ].
ToLowerInvariant ( );
}
}
else
{
theSwitch = dataSymbols [ n ];
// Look at the next parameter if it's there.
if ( currArg + 1 < args.Length )
{
currArg++;
dataValue = args [ currArg ];
// Take a look at dataValue to see if it starts
// with a switch character. If it does, that
// means this data argument is empty.
if ( true == StartsWithSwitchChar (
dataValue ) )
{
ss = SwitchStatus.Error;
break;
}
}
else
{
ss = SwitchStatus.Error;
break;
}
}
ss = OnSwitch ( theSwitch , dataValue );
}
else
{
ss = SwitchStatus.Error;
break;
}
}
else
{
// This is not a switch, notified the derived class of this
// "non-switch value"
ss = OnNonSwitch ( args [ currArg ] );
}
}
// Finished parsing arguments
if ( ss == SwitchStatus.NoError )
{
// No error occurred while parsing, let derived class perform a
// sanity check and return an appropriate status
ss = OnDoneParse ( );
}
if ( ss == SwitchStatus.ShowUsage )
{
// Status indicates that usage should be shown, show it
OnUsage ( null );
}
if ( ss == SwitchStatus.Error )
{
String errorValue = null;
if ( ( errorArg != -1 ) && ( errorArg != args.Length ) )
{
errorValue = args [ errorArg ];
}
// Status indicates that an error occurred, show it and the
// proper usage
OnUsage ( errorValue );
}
// Return whether all parsing was successful.
return ( ss == SwitchStatus.NoError );
}
}
}