2020-12-21 11:50:46 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Diagnostics ;
2021-04-27 22:41:48 -04:00
using System.Diagnostics.CodeAnalysis ;
2020-12-21 11:50:46 -04:00
using System.Linq ;
using System.Reflection ;
using System.Text ;
2022-03-24 16:35:00 -04:00
using Microsoft.Extensions.Logging ;
2020-12-21 11:50:46 -04:00
2020-12-21 23:07:37 -04:00
namespace EpicGames.Core
2020-12-21 11:50:46 -04:00
{
/// <summary>
/// Helper class to visualize an argument list
/// </summary>
class CommandLineArgumentListView
{
/// <summary>
/// The list of arguments
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
2022-03-24 16:35:00 -04:00
public string [ ] Arguments { get ; }
2020-12-21 11:50:46 -04:00
/// <summary>
/// Constructor
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="argumentList">The argument list to proxy</param>
public CommandLineArgumentListView ( CommandLineArguments argumentList )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
Arguments = argumentList . GetRawArray ( ) ;
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Exception thrown for invalid command line arguments
/// </summary>
public class CommandLineArgumentException : Exception
{
/// <summary>
/// Constructor
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="message">Message to display for this exception</param>
public CommandLineArgumentException ( string message )
: base ( message )
2020-12-21 11:50:46 -04:00
{
}
/// <summary>
/// Constructor
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="message">Message to display for this exception</param>
/// <param name="innerException">The inner exception</param>
public CommandLineArgumentException ( string message , Exception innerException )
: base ( message , innerException )
2020-12-21 11:50:46 -04:00
{
}
/// <summary>
/// Converts this exception to a string
/// </summary>
/// <returns>Exception message</returns>
public override string ToString ( )
{
return Message ;
}
}
/// <summary>
/// Stores a list of command line arguments, allowing efficient ad-hoc queries of particular options (eg. "-Flag") and retreival of typed values (eg. "-Foo=Bar"), as
/// well as attribute-driven application to fields with the [CommandLine] attribute applied.
///
/// Also tracks which arguments have been retrieved, allowing the display of diagnostic messages for invalid arguments.
/// </summary>
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(CommandLineArgumentListView))]
public class CommandLineArguments : IReadOnlyList < string > , IReadOnlyCollection < string > , IEnumerable < string > , IEnumerable
{
2022-01-06 09:05:06 -05:00
/// <summary>
/// Information about a property or field that can receive an argument
/// </summary>
class ArgumentTarget
{
public MemberInfo Member { get ; }
public Type ValueType { get ; }
public Action < object? , object? > SetValue { get ; }
public Func < object? , object? > GetValue { get ; }
public CommandLineAttribute [ ] Attributes { get ; }
2022-03-24 16:35:00 -04:00
public ArgumentTarget ( MemberInfo member , Type valueType , Action < object? , object? > setValue , Func < object? , object? > getValue , CommandLineAttribute [ ] attributes )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
Member = member ;
ValueType = valueType ;
SetValue = setValue ;
GetValue = getValue ;
Attributes = attributes ;
2022-01-06 09:05:06 -05:00
}
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// The raw array of arguments
/// </summary>
2022-03-24 16:35:00 -04:00
readonly string [ ] _arguments ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// Bitmask indicating which arguments are flags rather than values
/// </summary>
2022-03-24 16:35:00 -04:00
readonly BitArray _flagArguments ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// Bitmask indicating which arguments have been used, via calls to GetOption(), GetValues() etc...
/// </summary>
2022-03-24 16:35:00 -04:00
readonly BitArray _usedArguments ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// Dictionary of argument names (or prefixes, in the case of "-Foo=" style arguments) to their index into the arguments array.
/// </summary>
2022-03-24 16:35:00 -04:00
readonly Dictionary < string , int > _argumentToFirstIndex ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// For each argument which is seen more than once, keeps a list of indices for the second and subsequent arguments.
/// </summary>
2022-03-24 16:35:00 -04:00
readonly int [ ] _nextArgumentIndex ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// List of positional arguments
/// </summary>
2022-03-24 16:35:00 -04:00
readonly List < int > _positionalArgumentIndices = new List < int > ( ) ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// Array of characters that separate argument names from values
/// </summary>
2022-03-24 16:35:00 -04:00
static readonly char [ ] s_valueSeparators = { '=' , ':' } ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// Constructor
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="arguments">The raw list of arguments</param>
public CommandLineArguments ( string [ ] arguments )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
_arguments = arguments ;
_flagArguments = new BitArray ( arguments . Length ) ;
_usedArguments = new BitArray ( arguments . Length ) ;
2020-12-21 11:50:46 -04:00
// Clear the linked list of identical arguments
2022-03-24 16:35:00 -04:00
_nextArgumentIndex = new int [ arguments . Length ] ;
for ( int idx = 0 ; idx < arguments . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
_nextArgumentIndex [ idx ] = - 1 ;
2020-12-21 11:50:46 -04:00
}
// Temporarily store the index of the last matching argument
2022-03-24 16:35:00 -04:00
int [ ] lastArgumentIndex = new int [ arguments . Length ] ;
2020-12-21 11:50:46 -04:00
// Parse the argument array and build a lookup
2022-03-24 16:35:00 -04:00
_argumentToFirstIndex = new Dictionary < string , int > ( arguments . Length , StringComparer . OrdinalIgnoreCase ) ;
for ( int idx = 0 ; idx < arguments . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( arguments [ idx ] . Equals ( "--" , StringComparison . Ordinal ) )
2020-12-21 11:50:46 -04:00
{
// End of option arguments
2022-03-24 16:35:00 -04:00
MarkAsUsed ( idx + + ) ;
for ( ; idx < arguments . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
_positionalArgumentIndices . Add ( idx ) ;
2020-12-21 11:50:46 -04:00
}
break ;
}
2022-03-24 16:35:00 -04:00
else if ( arguments [ idx ] . StartsWith ( "-" , StringComparison . Ordinal ) )
2020-12-21 11:50:46 -04:00
{
// Option argument
2022-03-24 16:35:00 -04:00
int separatorIdx = arguments [ idx ] . IndexOfAny ( s_valueSeparators ) ;
if ( separatorIdx = = - 1 )
2020-12-21 11:50:46 -04:00
{
// Ignore duplicate -Option flags; they are harmless.
2022-03-24 16:35:00 -04:00
if ( _argumentToFirstIndex . ContainsKey ( arguments [ idx ] ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
_usedArguments . Set ( idx , true ) ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
_argumentToFirstIndex . Add ( arguments [ idx ] , idx ) ;
2020-12-21 11:50:46 -04:00
}
// Mark this argument as a flag
2022-03-24 16:35:00 -04:00
_flagArguments . Set ( idx , true ) ;
2020-12-21 11:50:46 -04:00
}
else
{
// Just take the part up to and including the separator character
2022-03-24 16:35:00 -04:00
string prefix = arguments [ idx ] . Substring ( 0 , separatorIdx + 1 ) ;
2020-12-21 11:50:46 -04:00
// Add the prefix to the argument lookup, or update the appropriate matching argument list if it's been seen before
2022-03-24 16:35:00 -04:00
int existingArgumentIndex ;
if ( _argumentToFirstIndex . TryGetValue ( prefix , out existingArgumentIndex ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
_nextArgumentIndex [ lastArgumentIndex [ existingArgumentIndex ] ] = idx ;
lastArgumentIndex [ existingArgumentIndex ] = idx ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
_argumentToFirstIndex . Add ( prefix , idx ) ;
lastArgumentIndex [ idx ] = idx ;
2020-12-21 11:50:46 -04:00
}
}
}
else
{
// Positional argument
2022-03-24 16:35:00 -04:00
_positionalArgumentIndices . Add ( idx ) ;
2020-12-21 11:50:46 -04:00
}
}
}
/// <summary>
/// The number of arguments in this list
/// </summary>
2022-03-24 16:35:00 -04:00
public int Count = > _arguments . Length ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// Access an argument by index
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="index">Index of the argument</param>
2020-12-21 11:50:46 -04:00
/// <returns>The argument at the given index</returns>
2022-03-24 16:35:00 -04:00
public string this [ int index ] = > _arguments [ index ] ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// Determines if an argument has been used
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="index">Index of the argument</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the argument has been used, false otherwise</returns>
2022-03-24 16:35:00 -04:00
public bool HasBeenUsed ( int index )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return _usedArguments . Get ( index ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Marks an argument as having been used
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="index">Index of the argument to mark as used</param>
public void MarkAsUsed ( int index )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
_usedArguments . Set ( index , true ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Marks an argument as not having been used
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="index">Index of the argument to mark as being unused</param>
public void MarkAsUnused ( int index )
2020-12-21 11:50:46 -04:00
{
2022-05-18 13:24:41 -04:00
_usedArguments . Set ( index , false ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Checks if the given option (eg. "-Foo") was specified on the command line.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="option">The option to look for</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the option was found, false otherwise.</returns>
2022-03-24 16:35:00 -04:00
public bool HasOption ( string option )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int index ;
if ( _argumentToFirstIndex . TryGetValue ( option , out index ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
_usedArguments . Set ( index , true ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
return false ;
}
/// <summary>
/// Checks for an argument prefixed with the given string is present.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if an argument with the given prefix was specified</returns>
2022-03-24 16:35:00 -04:00
public bool HasValue ( string prefix )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
CheckValidPrefix ( prefix ) ;
return _argumentToFirstIndex . ContainsKey ( prefix ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the positional argument at the given index
/// </summary>
/// <returns>Number of positional arguments</returns>
public int GetPositionalArgumentCount ( )
{
2022-03-24 16:35:00 -04:00
return _positionalArgumentIndices . Count ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the index of the numbered positional argument
/// </summary>
/// <param name="Index">Number of the positional argument</param>
/// <returns>Index of the positional argument</returns>
2022-03-24 16:35:00 -04:00
public int GetPositionalArgumentIndex ( int num )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return _positionalArgumentIndices [ num ] ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Attempts to read the next unused positional argument
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="argument">Receives the argument that was read, on success</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if an argument was read</returns>
2022-03-24 16:35:00 -04:00
public bool TryGetPositionalArgument ( [ NotNullWhen ( true ) ] out string? argument )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
for ( int idx = 0 ; idx < _positionalArgumentIndices . Count ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int index = _positionalArgumentIndices [ idx ] ;
if ( ! HasBeenUsed ( index ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
MarkAsUsed ( index ) ;
argument = _arguments [ index ] ;
2020-12-21 11:50:46 -04:00
return true ;
}
}
2022-03-24 16:35:00 -04:00
argument = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
/// <summary>
/// Returns all the positional arguments, and marks them as used
/// </summary>
/// <returns>Array of positional arguments</returns>
public string [ ] GetPositionalArguments ( )
{
2022-03-24 16:35:00 -04:00
string [ ] positionalArguments = new string [ _positionalArgumentIndices . Count ] ;
for ( int idx = 0 ; idx < positionalArguments . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int index = _positionalArgumentIndices [ idx ] ;
MarkAsUsed ( index ) ;
positionalArguments [ idx ] = _arguments [ index ] ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return positionalArguments ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix. Throws an exception if the argument was not specified.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
public string GetString ( string prefix )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
string? value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "Missing '{0}...' argument" , prefix ) ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix. Throws an exception if the argument was not specified.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
public int GetInteger ( string prefix )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "Missing '{0}...' argument" , prefix ) ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix. Throws an exception if the argument was not specified.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
public FileReference GetFileReference ( string prefix )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
FileReference ? value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "Missing '{0}...' argument" , prefix ) ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix. Throws an exception if the argument was not specified.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
public DirectoryReference GetDirectoryReference ( string prefix )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
DirectoryReference ? value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "Missing '{0}...' argument" , prefix ) ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix. Throws an exception if the argument was not specified.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
public T GetEnum < T > ( string prefix ) where T : struct
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
T value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "Missing '{0}...' argument" , prefix ) ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix, or a default value.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="defaultValue">Default value for the argument</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
[return: NotNullIfNotNull("defaultValue")]
public string? GetStringOrDefault ( string prefix , string? defaultValue )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
string? value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = defaultValue ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix, or a default value.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="defaultValue">Default value for the argument</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
public int GetIntegerOrDefault ( string prefix , int defaultValue )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = defaultValue ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix, or a default value.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="defaultValue">Default value for the argument</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
[return: NotNullIfNotNull("defaultValue")]
public FileReference ? GetFileReferenceOrDefault ( string prefix , FileReference ? defaultValue )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
FileReference ? value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = defaultValue ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix, or a default value.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="defaultValue">Default value for the argument</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
[return: NotNullIfNotNull("defaultValue")]
public DirectoryReference ? GetDirectoryReferenceOrDefault ( string prefix , DirectoryReference ? defaultValue )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
DirectoryReference ? value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = defaultValue ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the value specified by an argument with the given prefix, or a default value.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="defaultValue">Default value for the argument</param>
2020-12-21 11:50:46 -04:00
/// <returns>Value of the argument</returns>
2022-03-24 16:35:00 -04:00
public T GetEnumOrDefault < T > ( string prefix , T defaultValue ) where T : struct
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
T value ;
if ( ! TryGetValue ( prefix , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = defaultValue ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Tries to gets the value specified by an argument with the given prefix.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="value">Value of the argument, if found</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the argument was found (and Value was set), false otherwise.</returns>
2022-03-24 16:35:00 -04:00
public bool TryGetValue ( string prefix , [ NotNullWhen ( true ) ] out string? value )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
CheckValidPrefix ( prefix ) ;
2020-12-21 11:50:46 -04:00
2022-03-24 16:35:00 -04:00
int index ;
if ( ! _argumentToFirstIndex . TryGetValue ( prefix , out index ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
2022-03-24 16:35:00 -04:00
if ( _nextArgumentIndex [ index ] ! = - 1 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "Multiple {0}... arguments are specified" , prefix ) ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
_usedArguments . Set ( index , true ) ;
value = _arguments [ index ] . Substring ( prefix . Length ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
/// <summary>
/// Tries to gets the value specified by an argument with the given prefix.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="value">Value of the argument, if found</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the argument was found (and Value was set), false otherwise.</returns>
2022-03-24 16:35:00 -04:00
public bool TryGetValue ( string prefix , out int value )
2020-12-21 11:50:46 -04:00
{
// Try to get the string value of this argument
2022-03-24 16:35:00 -04:00
string? stringValue ;
if ( ! TryGetValue ( prefix , out stringValue ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = 0 ;
2020-12-21 11:50:46 -04:00
return false ;
}
// Try to parse it. If it fails, throw an exception.
try
{
2022-03-24 16:35:00 -04:00
value = Int32 . Parse ( stringValue ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
2022-03-24 16:35:00 -04:00
catch ( Exception ex )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "The argument '{0}{1}' does not specify a valid integer" , prefix , stringValue ) , ex ) ;
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Tries to gets the value specified by an argument with the given prefix.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="value">Value of the argument, if found</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the argument was found (and Value was set), false otherwise.</returns>
2022-03-24 16:35:00 -04:00
public bool TryGetValue ( string prefix , [ NotNullWhen ( true ) ] out FileReference ? value )
2020-12-21 11:50:46 -04:00
{
// Try to get the string value of this argument
2022-03-24 16:35:00 -04:00
string? stringValue ;
if ( ! TryGetValue ( prefix , out stringValue ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
// Try to parse it. If it fails, throw an exception.
try
{
2022-03-24 16:35:00 -04:00
value = new FileReference ( stringValue ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
2022-03-24 16:35:00 -04:00
catch ( Exception ex )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "The argument '{0}{1}' does not specify a valid file name" , prefix , stringValue ) , ex ) ;
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Tries to gets the value specified by an argument with the given prefix.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="value">Value of the argument, if found</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the argument was found (and Value was set), false otherwise.</returns>
2022-03-24 16:35:00 -04:00
public bool TryGetValue ( string prefix , [ NotNullWhen ( true ) ] out DirectoryReference ? value )
2020-12-21 11:50:46 -04:00
{
// Try to get the string value of this argument
2022-03-24 16:35:00 -04:00
string? stringValue ;
if ( ! TryGetValue ( prefix , out stringValue ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
// Try to parse it. If it fails, throw an exception.
try
{
2022-03-24 16:35:00 -04:00
value = new DirectoryReference ( stringValue ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
2022-03-24 16:35:00 -04:00
catch ( Exception ex )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "The argument '{0}{1}' does not specify a valid directory name" , prefix , stringValue ) , ex ) ;
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Tries to gets the value specified by an argument with the given prefix.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="value">Value of the argument, if found</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the argument was found (and Value was set), false otherwise.</returns>
2022-03-24 16:35:00 -04:00
public bool TryGetValue < T > ( string prefix , out T value ) where T : struct
2020-12-21 11:50:46 -04:00
{
// Try to get the string value of this argument
2022-03-24 16:35:00 -04:00
string? stringValue ;
if ( ! TryGetValue ( prefix , out stringValue ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = new T ( ) ;
2020-12-21 11:50:46 -04:00
return false ;
}
// Try to parse it. If it fails, throw an exception.
try
{
2022-03-24 16:35:00 -04:00
value = ( T ) Enum . Parse ( typeof ( T ) , stringValue , true ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
2022-03-24 16:35:00 -04:00
catch ( Exception ex )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "The argument '{0}{1}' does not specify a valid {2}" , prefix , stringValue , typeof ( T ) . Name ) , ex ) ;
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Returns all arguments with the given prefix.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
2020-12-21 11:50:46 -04:00
/// <returns>Sequence of values for the given prefix.</returns>
2022-03-24 16:35:00 -04:00
public IEnumerable < string > GetValues ( string prefix )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
CheckValidPrefix ( prefix ) ;
2020-12-21 11:50:46 -04:00
2022-03-24 16:35:00 -04:00
int index ;
if ( _argumentToFirstIndex . TryGetValue ( prefix , out index ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
for ( ; index ! = - 1 ; index = _nextArgumentIndex [ index ] )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
_usedArguments . Set ( index , true ) ;
yield return _arguments [ index ] . Substring ( prefix . Length ) ;
2020-12-21 11:50:46 -04:00
}
}
}
/// <summary>
/// Returns all arguments with the given prefix, allowing multiple arguments to be specified in a single argument with a separator character.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The argument prefix (eg. "-Foo="). Must end with an '=' character.</param>
/// <param name="separator">The separator character (eg. '+')</param>
2020-12-21 11:50:46 -04:00
/// <returns>Sequence of values for the given prefix.</returns>
2022-03-24 16:35:00 -04:00
public IEnumerable < string > GetValues ( string prefix , char separator )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
foreach ( string value in GetValues ( prefix ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
foreach ( string splitValue in value . Split ( separator ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
yield return splitValue ;
2020-12-21 11:50:46 -04:00
}
}
}
/// <summary>
/// Gets the prefix for a particular argument
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="target">The target hosting the attribute</param>
/// <param name="attribute">The attribute instance</param>
2020-12-21 11:50:46 -04:00
/// <returns>Prefix for this argument</returns>
2022-03-24 16:35:00 -04:00
private static string GetArgumentPrefix ( ArgumentTarget target , CommandLineAttribute attribute )
2020-12-21 11:50:46 -04:00
{
// Get the inner field type, unwrapping nullable types
2022-03-24 16:35:00 -04:00
Type valueType = target . ValueType ;
if ( valueType . IsGenericType & & valueType . GetGenericTypeDefinition ( ) = = typeof ( Nullable < > ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
valueType = valueType . GetGenericArguments ( ) [ 0 ] ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
string? prefix = attribute . Prefix ;
if ( prefix = = null )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( valueType = = typeof ( bool ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
prefix = String . Format ( "-{0}" , target . Member . Name ) ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
prefix = String . Format ( "-{0}=" , target . Member . Name ) ;
2020-12-21 11:50:46 -04:00
}
}
else
{
2022-03-24 16:35:00 -04:00
if ( valueType ! = typeof ( bool ) & & attribute . Value = = null & & ! prefix . EndsWith ( "=" ) & & ! prefix . EndsWith ( ":" ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
prefix + = "=" ;
2020-12-21 11:50:46 -04:00
}
}
2022-03-24 16:35:00 -04:00
return prefix ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Applies these arguments to fields with the [CommandLine] attribute in the given object.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="targetObject">The object to configure</param>
public void ApplyTo ( object targetObject )
2021-04-27 22:41:48 -04:00
{
2022-03-24 16:35:00 -04:00
ApplyTo ( targetObject , Log . Logger ) ;
2021-04-27 22:41:48 -04:00
}
/// <summary>
/// Applies these arguments to fields with the [CommandLine] attribute in the given object.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="targetObject">The object to configure</param>
/// <param name="logger">Sink for error/warning messages</param>
public void ApplyTo ( object targetObject , ILogger logger )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
List < string > missingArguments = new List < string > ( ) ;
2022-01-06 09:05:06 -05:00
// Build a mapping from name to field and attribute for this object
2022-03-24 16:35:00 -04:00
List < ArgumentTarget > targets = GetArgumentTargetsForType ( targetObject . GetType ( ) ) ;
foreach ( ArgumentTarget target in targets )
2020-12-21 11:50:46 -04:00
{
2022-01-06 09:05:06 -05:00
// If any attribute is required, keep track of it so we can include an error for it
2022-03-24 16:35:00 -04:00
string? requiredPrefix = null ;
2022-01-06 09:05:06 -05:00
// Keep track of whether a value has already been assigned to this field
2022-03-24 16:35:00 -04:00
string? assignedArgument = null ;
2022-01-06 09:05:06 -05:00
// Loop through all the attributes for different command line options that can modify it
2022-03-24 16:35:00 -04:00
foreach ( CommandLineAttribute attribute in target . Attributes )
2020-12-21 11:50:46 -04:00
{
2022-01-06 09:05:06 -05:00
// Get the appropriate prefix for this attribute
2022-03-24 16:35:00 -04:00
string prefix = GetArgumentPrefix ( target , attribute ) ;
2020-12-21 11:50:46 -04:00
2022-01-06 09:05:06 -05:00
// Get the value with the correct prefix
2022-03-24 16:35:00 -04:00
int firstIndex ;
if ( _argumentToFirstIndex . TryGetValue ( prefix , out firstIndex ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
for ( int index = firstIndex ; index ! = - 1 ; index = _nextArgumentIndex [ index ] )
2020-12-21 11:50:46 -04:00
{
2022-01-06 09:05:06 -05:00
// Get the argument text
2022-03-24 16:35:00 -04:00
string argument = _arguments [ index ] ;
2022-01-06 09:05:06 -05:00
// Get the text for this value
2022-03-24 16:35:00 -04:00
string valueText ;
if ( attribute . Value ! = null )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
valueText = attribute . Value ;
2022-01-06 09:05:06 -05:00
}
2022-03-24 16:35:00 -04:00
else if ( _flagArguments . Get ( index ) )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
valueText = "true" ;
2022-01-06 09:05:06 -05:00
}
else
{
2022-03-24 16:35:00 -04:00
valueText = argument . Substring ( prefix . Length ) ;
2022-01-06 09:05:06 -05:00
}
2020-12-21 11:50:46 -04:00
2022-01-06 09:05:06 -05:00
// Apply the value to the field
2022-03-24 16:35:00 -04:00
if ( attribute . ListSeparator = = 0 )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
if ( ApplyArgument ( targetObject , target , argument , valueText , assignedArgument , logger ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
assignedArgument = argument ;
2020-12-21 11:50:46 -04:00
}
2022-01-06 09:05:06 -05:00
}
else
{
2022-03-24 16:35:00 -04:00
foreach ( string itemValueText in valueText . Split ( attribute . ListSeparator ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( ApplyArgument ( targetObject , target , argument , itemValueText , assignedArgument , logger ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
assignedArgument = argument ;
2020-12-21 11:50:46 -04:00
}
}
}
2022-01-06 09:05:06 -05:00
// Mark this argument as used
2022-03-24 16:35:00 -04:00
_usedArguments . Set ( index , true ) ;
2020-12-21 11:50:46 -04:00
}
}
2022-01-06 09:05:06 -05:00
// If this attribute is marked as required, keep track of it so we can warn if the field is not assigned to
2022-03-24 16:35:00 -04:00
if ( attribute . Required & & requiredPrefix = = null )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
requiredPrefix = prefix ;
2020-12-21 11:50:46 -04:00
}
}
2022-01-06 09:05:06 -05:00
// Make sure that this field has been assigned to
2022-03-24 16:35:00 -04:00
if ( assignedArgument = = null & & requiredPrefix ! = null )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
missingArguments . Add ( requiredPrefix ) ;
2022-01-06 09:05:06 -05:00
}
2020-12-21 11:50:46 -04:00
}
// If any arguments were missing, print an error about them
2022-03-24 16:35:00 -04:00
if ( missingArguments . Count > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( missingArguments . Count = = 1 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "Missing {0} argument" , missingArguments [ 0 ] . Replace ( "=" , "=..." ) ) ) ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
throw new CommandLineArgumentException ( String . Format ( "Missing {0} arguments" , StringUtils . FormatList ( missingArguments . Select ( x = > x . Replace ( "=" , "=..." ) ) ) ) ) ;
2020-12-21 11:50:46 -04:00
}
}
}
2022-01-10 13:44:32 -05:00
/// <summary>
/// Applies these arguments to fields with the [CommandLine] attribute in the given object.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="logger">Sink for error/warning messages</param>
public T ApplyTo < T > ( ILogger logger ) where T : new ( )
2022-01-10 13:44:32 -05:00
{
2022-03-24 16:35:00 -04:00
T obj = new T ( ) ;
ApplyTo ( obj , logger ) ;
return obj ;
2022-01-10 13:44:32 -05:00
}
2022-03-24 16:35:00 -04:00
static List < ArgumentTarget > GetArgumentTargetsForType ( Type ? targetType )
2022-01-10 13:44:32 -05:00
{
2022-03-24 16:35:00 -04:00
List < ArgumentTarget > targets = new List < ArgumentTarget > ( ) ;
while ( targetType ! = null & & targetType ! = typeof ( object ) )
2022-01-10 13:44:32 -05:00
{
2022-03-24 16:35:00 -04:00
foreach ( FieldInfo fieldInfo in targetType . GetFields ( BindingFlags . Instance | BindingFlags . GetField | BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . DeclaredOnly ) )
2022-01-10 13:44:32 -05:00
{
2022-03-24 16:35:00 -04:00
IEnumerable < CommandLineAttribute > attributes = fieldInfo . GetCustomAttributes < CommandLineAttribute > ( ) ;
if ( attributes . Any ( ) )
2022-01-10 13:44:32 -05:00
{
2022-03-24 16:35:00 -04:00
targets . Add ( new ArgumentTarget ( fieldInfo , fieldInfo . FieldType , fieldInfo . SetValue , fieldInfo . GetValue , attributes . ToArray ( ) ) ) ;
2022-01-10 13:44:32 -05:00
}
}
2022-03-24 16:35:00 -04:00
foreach ( PropertyInfo propertyInfo in targetType . GetProperties ( BindingFlags . Instance | BindingFlags . GetProperty | BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . DeclaredOnly ) )
2022-01-10 13:44:32 -05:00
{
2022-03-24 16:35:00 -04:00
IEnumerable < CommandLineAttribute > attributes = propertyInfo . GetCustomAttributes < CommandLineAttribute > ( ) ;
if ( attributes . Any ( ) )
2022-01-10 13:44:32 -05:00
{
2022-03-24 16:35:00 -04:00
targets . Add ( new ArgumentTarget ( propertyInfo , propertyInfo . PropertyType , propertyInfo . SetValue , propertyInfo . GetValue , attributes . ToArray ( ) ) ) ;
2022-01-10 13:44:32 -05:00
}
}
2022-03-24 16:35:00 -04:00
targetType = targetType . BaseType ;
2022-01-10 13:44:32 -05:00
}
2022-03-24 16:35:00 -04:00
return targets ;
2022-01-10 13:44:32 -05:00
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Gets help text for the arguments of a given type
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="type">The type to find parameters for</param>
2020-12-21 11:50:46 -04:00
/// <returns>List of parameters</returns>
2022-03-24 16:35:00 -04:00
public static List < KeyValuePair < string , string > > GetParameters ( Type type )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
List < KeyValuePair < string , string > > parameters = new List < KeyValuePair < string , string > > ( ) ;
2022-01-06 09:05:06 -05:00
2022-03-24 16:35:00 -04:00
List < ArgumentTarget > targets = GetArgumentTargetsForType ( type ) ;
foreach ( ArgumentTarget target in targets )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
StringBuilder descriptionBuilder = new StringBuilder ( ) ;
foreach ( DescriptionAttribute attribute in target . Member . GetCustomAttributes < DescriptionAttribute > ( ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( descriptionBuilder . Length > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
descriptionBuilder . Append ( "\n" ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
descriptionBuilder . Append ( attribute . Description ) ;
2022-01-06 09:05:06 -05:00
}
2022-03-24 16:35:00 -04:00
string description = descriptionBuilder . ToString ( ) ;
if ( description . Length = = 0 )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
description = "No description available." ;
2022-01-06 09:05:06 -05:00
}
2022-03-24 16:35:00 -04:00
foreach ( CommandLineAttribute attribute in target . Attributes )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
string prefix = GetArgumentPrefix ( target , attribute ) ;
if ( prefix . EndsWith ( "=" , StringComparison . Ordinal ) )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
prefix + = "..." ;
2022-01-06 09:05:06 -05:00
}
2022-03-24 16:35:00 -04:00
parameters . Add ( new KeyValuePair < string , string > ( prefix , description ) ) ;
2020-12-21 11:50:46 -04:00
}
}
2022-03-24 16:35:00 -04:00
return parameters ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Quotes a command line argument, if necessary
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="argument">The argument that may need quoting</param>
2020-12-21 11:50:46 -04:00
/// <returns>Argument which is safe to pass on the command line</returns>
2022-03-24 16:35:00 -04:00
public static string Quote ( string argument )
2020-12-21 11:50:46 -04:00
{
// See if the entire string is quoted correctly
bool bInQuotes = false ;
2022-03-24 16:35:00 -04:00
for ( int idx = 0 ; ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( idx = = argument . Length )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return argument ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
else if ( argument [ idx ] = = '\"' )
2020-12-21 11:50:46 -04:00
{
bInQuotes ^ = true ;
}
2022-03-24 16:35:00 -04:00
else if ( argument [ idx ] = = ' ' )
2020-12-21 11:50:46 -04:00
{
break ;
}
}
// Try to insert a quote after the argument string
2022-03-24 16:35:00 -04:00
if ( argument [ 0 ] = = '-' )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
for ( int idx = 1 ; idx < argument . Length & & argument [ idx ] ! = ' ' ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( argument [ idx ] = = '=' )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return String . Format ( "{0}=\"{1}\"" , argument . Substring ( 0 , idx ) , argument . Substring ( idx + 1 ) . Replace ( "\"" , "\\\"" ) ) ;
2020-12-21 11:50:46 -04:00
}
}
}
// Quote the whole thing
2022-03-24 16:35:00 -04:00
return "\"" + argument . Replace ( "\"" , "\\\"" ) + "\"" ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Joins the given arguments into a command line
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="arguments">List of command line arguments</param>
2020-12-21 11:50:46 -04:00
/// <returns>Joined command line</returns>
2022-03-24 16:35:00 -04:00
public static string Join ( IEnumerable < string > arguments )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
StringBuilder result = new StringBuilder ( ) ;
foreach ( string argument in arguments )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( result . Length > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
result . Append ( ' ' ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
result . Append ( Quote ( argument ) ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return result . ToString ( ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Splits a command line into individual arguments
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="commandLine">The command line text</param>
2020-12-21 11:50:46 -04:00
/// <returns>Array of arguments</returns>
2022-03-24 16:35:00 -04:00
public static string [ ] Split ( string commandLine )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
StringBuilder argument = new StringBuilder ( ) ;
2020-12-21 11:50:46 -04:00
2022-03-24 16:35:00 -04:00
List < string > arguments = new List < string > ( ) ;
for ( int idx = 0 ; idx < commandLine . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( ! Char . IsWhiteSpace ( commandLine [ idx ] ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
argument . Clear ( ) ;
for ( bool bInQuotes = false ; idx < commandLine . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( commandLine [ idx ] = = '\"' )
2020-12-21 11:50:46 -04:00
{
bInQuotes ^ = true ;
}
2022-03-24 16:35:00 -04:00
else if ( ! bInQuotes & & Char . IsWhiteSpace ( commandLine [ idx ] ) )
2020-12-21 11:50:46 -04:00
{
break ;
}
else
{
2022-03-24 16:35:00 -04:00
argument . Append ( commandLine [ idx ] ) ;
2020-12-21 11:50:46 -04:00
}
}
2022-03-24 16:35:00 -04:00
arguments . Add ( argument . ToString ( ) ) ;
2020-12-21 11:50:46 -04:00
}
}
2022-03-24 16:35:00 -04:00
return arguments . ToArray ( ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Appends the given arguments to the current argument list
/// </summary>
/// <param name="Arguments">The arguments to add</param>
/// <returns>New argument list</returns>
2022-03-24 16:35:00 -04:00
public CommandLineArguments Append ( IEnumerable < string > appendArguments )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
CommandLineArguments newArguments = new CommandLineArguments ( Enumerable . Concat ( _arguments , appendArguments ) . ToArray ( ) ) ;
for ( int idx = 0 ; idx < _arguments . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( HasBeenUsed ( idx ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
newArguments . MarkAsUsed ( idx ) ;
2020-12-21 11:50:46 -04:00
}
}
2022-03-24 16:35:00 -04:00
return newArguments ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Retrieves all arguments with the given prefix, and returns the remaining a list of strings
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">Prefix for the arguments to remove</param>
/// <param name="values">Receives a list of values with the given prefix</param>
2020-12-21 11:50:46 -04:00
/// <returns>New argument list</returns>
2022-03-24 16:35:00 -04:00
public CommandLineArguments Remove ( string prefix , out List < string > values )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
values = new List < string > ( ) ;
2020-12-21 11:50:46 -04:00
// Split the arguments into the values array and an array of new arguments
2022-03-24 16:35:00 -04:00
int [ ] newArgumentIndex = new int [ _arguments . Length ] ;
List < string > newArgumentList = new List < string > ( _arguments . Length ) ;
for ( int idx = 0 ; idx < _arguments . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
string argument = _arguments [ idx ] ;
if ( argument . StartsWith ( prefix , StringComparison . OrdinalIgnoreCase ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
newArgumentIndex [ idx ] = - 1 ;
values . Add ( argument . Substring ( prefix . Length ) ) ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
newArgumentIndex [ idx ] = newArgumentList . Count ;
newArgumentList . Add ( argument ) ;
2020-12-21 11:50:46 -04:00
}
}
// Create the new argument list, and mark the same arguments as used
2022-03-24 16:35:00 -04:00
CommandLineArguments newArguments = new CommandLineArguments ( newArgumentList . ToArray ( ) ) ;
for ( int idx = 0 ; idx < _arguments . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( HasBeenUsed ( idx ) & & newArgumentIndex [ idx ] ! = - 1 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
newArguments . MarkAsUsed ( newArgumentIndex [ idx ] ) ;
2020-12-21 11:50:46 -04:00
}
}
2022-03-24 16:35:00 -04:00
return newArguments ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Checks that there are no unused arguments (and warns if there are)
/// </summary>
public void CheckAllArgumentsUsed ( )
2021-04-27 22:41:48 -04:00
{
CheckAllArgumentsUsed ( Log . Logger ) ;
}
/// <summary>
/// Checks that there are no unused arguments (and warns if there are)
/// </summary>
2022-03-24 16:35:00 -04:00
public void CheckAllArgumentsUsed ( ILogger logger )
2020-12-21 11:50:46 -04:00
{
// Find all the unused arguments
2022-03-24 16:35:00 -04:00
List < string > remainingArguments = new List < string > ( ) ;
for ( int idx = 0 ; idx < _arguments . Length ; idx + + )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( ! _usedArguments [ idx ] )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
remainingArguments . Add ( _arguments [ idx ] ) ;
2020-12-21 11:50:46 -04:00
}
}
// Output a warning
2022-03-24 16:35:00 -04:00
if ( remainingArguments . Count > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( remainingArguments . Count = = 1 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
logger . LogWarning ( String . Format ( "Invalid argument: {0}" , remainingArguments [ 0 ] ) ) ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
logger . LogWarning ( String . Format ( "Invalid arguments:\n{0}" , String . Join ( "\n" , remainingArguments ) ) ) ;
2020-12-21 11:50:46 -04:00
}
}
}
UnrealBuildTool:
Add a -Help option that prints descriptions of global options.
(Tool mode options are not available - support for those may be added in a future CL)
Example output:
Global options:
-Help : Display this help.
-Verbose : Increase output verbosity
-VeryVerbose : Increase output verbosity more
-Log : Specify a log file location instead of the default Engine/Programs/UnrealBuildTool/Log.txt
-Timestamps : Include timestamps in the log
-FromMsBuild : Format messages for msbuild
-Progress : Write progress messages in a format that can be parsed by other programs
-NoMutex : Allow more than one instance of the program to run at once
-WaitMutex : Wait for another instance to finish and then start, rather than aborting immediately
-RemoteIni : Remote tool ini directory
-Mode= : Select tool mode. One of the following (default tool mode is "Build"):
AggregateParsedTimingInfo, Build, Clean, Deploy, Execute, GenerateClangDatabase, GenerateProjectFiles,
IOSPostBuildSync, JsonExport, ParseMsvcTimingInfo, PVSGather, QueryTargets, SetupPlatforms,
ValidatePlatforms, WriteDocumentation, WriteMetadata
-Clean : Clean build products. Equivalent to -Mode=Clean
-ProjectFiles : Generate project files based on IDE preference. Equivalent to -Mode=GenerateProjectFiles
-ProjectFileFormat= : Generate project files in specified format. May be used multiple times.
-Makefile : Generate Linux Makefile
-CMakefile : Generate project files for CMake
-QMakefile : Generate project files for QMake
-KDevelopfile : Generate project files for KDevelop
-CodeliteFiles : Generate project files for Codelite
-XCodeProjectFiles : Generate project files for XCode
-EddieProjectFiles : Generate project files for Eddie
-VSCode : Generate project files for Visual Studio Code
-VSMac : Generate project files for Visual Studio Mac
-CLion : Generate project files for CLion
-Rider : Generate project files for Rider
#jira none
[CL 17018675 by jonathan adamczewski in ue5-main branch]
2021-08-02 14:45:39 -04:00
/// <summary>
/// Checks to see if any arguments are used
/// </summary>
/// <returns></returns>
public bool AreAnyArgumentsUsed ( )
{
2022-03-24 16:35:00 -04:00
return _usedArguments . Cast < bool > ( ) . Count ( b = > b ) ! = 0 ;
UnrealBuildTool:
Add a -Help option that prints descriptions of global options.
(Tool mode options are not available - support for those may be added in a future CL)
Example output:
Global options:
-Help : Display this help.
-Verbose : Increase output verbosity
-VeryVerbose : Increase output verbosity more
-Log : Specify a log file location instead of the default Engine/Programs/UnrealBuildTool/Log.txt
-Timestamps : Include timestamps in the log
-FromMsBuild : Format messages for msbuild
-Progress : Write progress messages in a format that can be parsed by other programs
-NoMutex : Allow more than one instance of the program to run at once
-WaitMutex : Wait for another instance to finish and then start, rather than aborting immediately
-RemoteIni : Remote tool ini directory
-Mode= : Select tool mode. One of the following (default tool mode is "Build"):
AggregateParsedTimingInfo, Build, Clean, Deploy, Execute, GenerateClangDatabase, GenerateProjectFiles,
IOSPostBuildSync, JsonExport, ParseMsvcTimingInfo, PVSGather, QueryTargets, SetupPlatforms,
ValidatePlatforms, WriteDocumentation, WriteMetadata
-Clean : Clean build products. Equivalent to -Mode=Clean
-ProjectFiles : Generate project files based on IDE preference. Equivalent to -Mode=GenerateProjectFiles
-ProjectFileFormat= : Generate project files in specified format. May be used multiple times.
-Makefile : Generate Linux Makefile
-CMakefile : Generate project files for CMake
-QMakefile : Generate project files for QMake
-KDevelopfile : Generate project files for KDevelop
-CodeliteFiles : Generate project files for Codelite
-XCodeProjectFiles : Generate project files for XCode
-EddieProjectFiles : Generate project files for Eddie
-VSCode : Generate project files for Visual Studio Code
-VSMac : Generate project files for Visual Studio Mac
-CLion : Generate project files for CLion
-Rider : Generate project files for Rider
#jira none
[CL 17018675 by jonathan adamczewski in ue5-main branch]
2021-08-02 14:45:39 -04:00
}
2022-01-06 09:05:06 -05:00
/// <summary>
/// Checks to see if any arguments are used
/// </summary>
/// <returns></returns>
public IEnumerable < string > GetUnusedArguments ( )
{
2022-03-24 16:35:00 -04:00
for ( int idx = 0 ; idx < _arguments . Length ; idx + + )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
if ( ! _usedArguments [ idx ] )
2022-01-06 09:05:06 -05:00
{
2022-03-24 16:35:00 -04:00
yield return _arguments [ idx ] ;
2022-01-06 09:05:06 -05:00
}
}
}
UnrealBuildTool:
Add a -Help option that prints descriptions of global options.
(Tool mode options are not available - support for those may be added in a future CL)
Example output:
Global options:
-Help : Display this help.
-Verbose : Increase output verbosity
-VeryVerbose : Increase output verbosity more
-Log : Specify a log file location instead of the default Engine/Programs/UnrealBuildTool/Log.txt
-Timestamps : Include timestamps in the log
-FromMsBuild : Format messages for msbuild
-Progress : Write progress messages in a format that can be parsed by other programs
-NoMutex : Allow more than one instance of the program to run at once
-WaitMutex : Wait for another instance to finish and then start, rather than aborting immediately
-RemoteIni : Remote tool ini directory
-Mode= : Select tool mode. One of the following (default tool mode is "Build"):
AggregateParsedTimingInfo, Build, Clean, Deploy, Execute, GenerateClangDatabase, GenerateProjectFiles,
IOSPostBuildSync, JsonExport, ParseMsvcTimingInfo, PVSGather, QueryTargets, SetupPlatforms,
ValidatePlatforms, WriteDocumentation, WriteMetadata
-Clean : Clean build products. Equivalent to -Mode=Clean
-ProjectFiles : Generate project files based on IDE preference. Equivalent to -Mode=GenerateProjectFiles
-ProjectFileFormat= : Generate project files in specified format. May be used multiple times.
-Makefile : Generate Linux Makefile
-CMakefile : Generate project files for CMake
-QMakefile : Generate project files for QMake
-KDevelopfile : Generate project files for KDevelop
-CodeliteFiles : Generate project files for Codelite
-XCodeProjectFiles : Generate project files for XCode
-EddieProjectFiles : Generate project files for Eddie
-VSCode : Generate project files for Visual Studio Code
-VSMac : Generate project files for Visual Studio Mac
-CLion : Generate project files for CLion
-Rider : Generate project files for Rider
#jira none
[CL 17018675 by jonathan adamczewski in ue5-main branch]
2021-08-02 14:45:39 -04:00
/// <summary>
/// Count the number of value (non-flag) arguments on the command line
/// </summary>
/// <returns></returns>
public int CountValueArguments ( )
{
2022-03-24 16:35:00 -04:00
return _flagArguments . Cast < bool > ( ) . Count ( b = > ! b ) ;
UnrealBuildTool:
Add a -Help option that prints descriptions of global options.
(Tool mode options are not available - support for those may be added in a future CL)
Example output:
Global options:
-Help : Display this help.
-Verbose : Increase output verbosity
-VeryVerbose : Increase output verbosity more
-Log : Specify a log file location instead of the default Engine/Programs/UnrealBuildTool/Log.txt
-Timestamps : Include timestamps in the log
-FromMsBuild : Format messages for msbuild
-Progress : Write progress messages in a format that can be parsed by other programs
-NoMutex : Allow more than one instance of the program to run at once
-WaitMutex : Wait for another instance to finish and then start, rather than aborting immediately
-RemoteIni : Remote tool ini directory
-Mode= : Select tool mode. One of the following (default tool mode is "Build"):
AggregateParsedTimingInfo, Build, Clean, Deploy, Execute, GenerateClangDatabase, GenerateProjectFiles,
IOSPostBuildSync, JsonExport, ParseMsvcTimingInfo, PVSGather, QueryTargets, SetupPlatforms,
ValidatePlatforms, WriteDocumentation, WriteMetadata
-Clean : Clean build products. Equivalent to -Mode=Clean
-ProjectFiles : Generate project files based on IDE preference. Equivalent to -Mode=GenerateProjectFiles
-ProjectFileFormat= : Generate project files in specified format. May be used multiple times.
-Makefile : Generate Linux Makefile
-CMakefile : Generate project files for CMake
-QMakefile : Generate project files for QMake
-KDevelopfile : Generate project files for KDevelop
-CodeliteFiles : Generate project files for Codelite
-XCodeProjectFiles : Generate project files for XCode
-EddieProjectFiles : Generate project files for Eddie
-VSCode : Generate project files for Visual Studio Code
-VSMac : Generate project files for Visual Studio Mac
-CLion : Generate project files for CLion
-Rider : Generate project files for Rider
#jira none
[CL 17018675 by jonathan adamczewski in ue5-main branch]
2021-08-02 14:45:39 -04:00
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Checks that a given string is a valid argument prefix
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="prefix">The prefix to check</param>
private static void CheckValidPrefix ( string prefix )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( prefix . Length = = 0 )
2020-12-21 11:50:46 -04:00
{
throw new ArgumentException ( "Argument prefix cannot be empty." ) ;
}
2022-03-24 16:35:00 -04:00
else if ( prefix [ 0 ] ! = '-' )
2020-12-21 11:50:46 -04:00
{
throw new ArgumentException ( "Argument prefix must begin with a hyphen." ) ;
}
2022-03-24 16:35:00 -04:00
else if ( ! s_valueSeparators . Contains ( prefix [ ^ 1 ] ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
throw new ArgumentException ( String . Format ( "Argument prefix must end with '{0}'" , String . Join ( "' or '" , s_valueSeparators ) ) ) ;
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Parses and assigns a value to a field
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="targetObject">The target object to assign values to</param>
/// <param name="target">The target to assign the value to</param>
/// <param name="argumentText">The full argument text</param>
/// <param name="valueText">Argument text</param>
/// <param name="previousArgumentText">The previous text used to configure this field</param>
/// <param name="logger">Logger for error/warning messages</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the value was assigned to the field, false otherwise</returns>
2022-03-24 16:35:00 -04:00
private static bool ApplyArgument ( object targetObject , ArgumentTarget target , string argumentText , string valueText , string? previousArgumentText , ILogger logger )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
Type valueType = target . ValueType ;
2020-12-21 11:50:46 -04:00
// Check if the field type implements ICollection<>. If so, we can take multiple values.
2022-03-24 16:35:00 -04:00
Type ? collectionType = null ;
foreach ( Type interfaceType in valueType . GetInterfaces ( ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( interfaceType . IsGenericType & & interfaceType . GetGenericTypeDefinition ( ) = = typeof ( ICollection < > ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
valueType = interfaceType . GetGenericArguments ( ) [ 0 ] ;
collectionType = interfaceType ;
2020-12-21 11:50:46 -04:00
break ;
}
}
// Try to parse the value
2022-03-24 16:35:00 -04:00
object? value ;
if ( ! TryParseValue ( valueType , valueText , out value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
logger . LogWarning ( "Unable to parse value for argument '{0}'." , argumentText ) ;
2020-12-21 11:50:46 -04:00
return false ;
}
// Try to assign values to the target field
2022-03-24 16:35:00 -04:00
if ( collectionType = = null )
2020-12-21 11:50:46 -04:00
{
// Check if this field has already been assigned to. Output a warning if the previous value is in conflict with the new one.
2022-03-24 16:35:00 -04:00
if ( previousArgumentText ! = null )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
object? previousValue = target . GetValue ( targetObject ) ;
if ( ! Object . Equals ( previousValue , value ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
logger . LogWarning ( "Argument '{0}' conflicts with '{1}'; ignoring." , argumentText , previousArgumentText ) ;
2020-12-21 11:50:46 -04:00
}
return false ;
}
// Set the value on the target object
2022-03-24 16:35:00 -04:00
target . SetValue ( targetObject , value ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
else
{
// Call the 'Add' method on the collection
2022-03-24 16:35:00 -04:00
collectionType . InvokeMember ( "Add" , BindingFlags . InvokeMethod , null , target . GetValue ( targetObject ) , new object [ ] { value } ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
}
/// <summary>
/// Attempts to parse the given string to a value
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="fieldType">Type of the field to convert to</param>
/// <param name="text">The value text</param>
/// <param name="value">On success, contains the parsed object</param>
2020-12-21 11:50:46 -04:00
/// <returns>True if the text could be parsed, false otherwise</returns>
2022-03-24 16:35:00 -04:00
private static bool TryParseValue ( Type fieldType , string text , [ NotNullWhen ( true ) ] out object? value )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( fieldType . IsGenericType & & fieldType . GetGenericTypeDefinition ( ) = = typeof ( Nullable < > ) )
2020-12-21 11:50:46 -04:00
{
// Try to parse the inner type instead
2022-03-24 16:35:00 -04:00
return TryParseValue ( fieldType . GetGenericArguments ( ) [ 0 ] , text , out value ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
else if ( fieldType . IsEnum )
2020-12-21 11:50:46 -04:00
{
// Special handling for enums; parse the value ignoring case.
try
{
2022-03-24 16:35:00 -04:00
value = Enum . Parse ( fieldType , text , true ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
catch ( ArgumentException )
{
2022-03-24 16:35:00 -04:00
value = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
}
2022-03-24 16:35:00 -04:00
else if ( fieldType = = typeof ( FileReference ) )
2020-12-21 11:50:46 -04:00
{
// Construct a file reference from the string
try
{
2022-03-24 16:35:00 -04:00
value = new FileReference ( text ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
catch
{
2022-03-24 16:35:00 -04:00
value = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
}
2022-03-24 16:35:00 -04:00
else if ( fieldType = = typeof ( DirectoryReference ) )
2020-12-21 11:50:46 -04:00
{
// Construct a file reference from the string
try
{
2022-03-24 16:35:00 -04:00
value = new DirectoryReference ( text ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
catch
{
2022-03-24 16:35:00 -04:00
value = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
}
2022-03-24 16:35:00 -04:00
else if ( fieldType = = typeof ( TimeSpan ) )
2020-12-21 11:50:46 -04:00
{
// Construct a time span form the string
2022-03-24 16:35:00 -04:00
double floatValue ;
if ( text . EndsWith ( "h" , StringComparison . OrdinalIgnoreCase ) & & Double . TryParse ( text . Substring ( 0 , text . Length - 1 ) , out floatValue ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = TimeSpan . FromHours ( floatValue ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
2022-03-24 16:35:00 -04:00
else if ( text . EndsWith ( "m" , StringComparison . OrdinalIgnoreCase ) & & Double . TryParse ( text . Substring ( 0 , text . Length - 1 ) , out floatValue ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = TimeSpan . FromMinutes ( floatValue ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
2022-03-24 16:35:00 -04:00
else if ( text . EndsWith ( "s" , StringComparison . OrdinalIgnoreCase ) & & Double . TryParse ( text . Substring ( 0 , text . Length - 1 ) , out floatValue ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = TimeSpan . FromSeconds ( floatValue ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
2022-03-24 16:35:00 -04:00
TimeSpan timeSpanValue ;
if ( TimeSpan . TryParse ( text , out timeSpanValue ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = timeSpanValue ;
2020-12-21 11:50:46 -04:00
return true ;
}
else
{
2022-03-24 16:35:00 -04:00
value = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
}
else
{
// First check for a TypeConverter
2022-03-24 16:35:00 -04:00
TypeConverter typeConverter = TypeDescriptor . GetConverter ( fieldType ) ;
if ( typeConverter . CanConvertFrom ( typeof ( string ) ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
value = typeConverter . ConvertFrom ( text ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
// Otherwise let the framework convert between types
try
{
2022-03-24 16:35:00 -04:00
value = Convert . ChangeType ( text , fieldType ) ;
2020-12-21 11:50:46 -04:00
return true ;
}
catch ( InvalidCastException )
{
2022-03-24 16:35:00 -04:00
value = null ;
2020-12-21 11:50:46 -04:00
return false ;
}
}
}
/// <summary>
/// Obtains an enumerator for the argument list
/// </summary>
/// <returns>IEnumerator interface</returns>
IEnumerator IEnumerable . GetEnumerator ( )
{
2022-03-24 16:35:00 -04:00
return _arguments . GetEnumerator ( ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Obtains an enumerator for the argument list
/// </summary>
/// <returns>Generic IEnumerator interface</returns>
public IEnumerator < string > GetEnumerator ( )
{
2022-03-24 16:35:00 -04:00
return ( ( IEnumerable < string > ) _arguments ) . GetEnumerator ( ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Gets the raw argument array
/// </summary>
/// <returns>Array of arguments</returns>
public string [ ] GetRawArray ( )
{
2022-03-24 16:35:00 -04:00
return _arguments ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Takes a command line argument and adds quotes if necessary
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="argument">The command line argument</param>
2020-12-21 11:50:46 -04:00
/// <returns>The command line argument with quotes inserted to escape it if necessary</returns>
2022-03-24 16:35:00 -04:00
public static void Append ( StringBuilder commandLine , string argument )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( commandLine . Length > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
commandLine . Append ( ' ' ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
int spaceIdx = argument . IndexOf ( ' ' ) ;
if ( spaceIdx = = - 1 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
commandLine . Append ( argument ) ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
int equalsIdx = argument . IndexOf ( '=' ) ;
if ( equalsIdx = = - 1 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
commandLine . AppendFormat ( "\"{0}\"" , argument ) ;
2020-12-21 11:50:46 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
commandLine . AppendFormat ( "{0}\"{1}\"" , argument . Substring ( 0 , equalsIdx + 1 ) , argument . Substring ( equalsIdx + 1 ) ) ;
2020-12-21 11:50:46 -04:00
}
}
}
/// <summary>
/// Converts this string to
/// </summary>
/// <returns></returns>
public override string ToString ( )
{
2022-03-24 16:35:00 -04:00
StringBuilder result = new StringBuilder ( ) ;
foreach ( string argument in _arguments )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
Append ( result , argument ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
return result . ToString ( ) ;
2020-12-21 11:50:46 -04:00
}
}
}