Ahora vamos con la manera tediosa, pero más eficiente y sobre todo organizada.
Primero de todo, creamos un Type para representar un parámetro command-line.
Lo que deberiamos tener en cuenta en el diseño del modelo es al menos lo siguiente:
- El nombre del parámetro
- El símbolo que separa al nombre del parámetro del valor que indique el usuario en el argumento.
- El valor por defecto del parámetro (para parámetros que son opcionales, o también para argumentos vacíos. )
- El valor que le da el usuario (valor que parsearemos en los argumentos de la aplicación)
- Un flag que indique si este parámetro es opcional o no. Si no es opcional entonces deberiamos mostrar un error cuando el usuario no usa este parámetro.
( A la lista que cada cual le añada otros factores que sean importantes para sus necesidades, como pro ejemplo el orden en el que se reciben los parámetros en los argumentos. )
Ahí va (lo he convertido a C# pero no he testeado la sintaxis) :
// ***********************************************************************
// Author : Elektro
// Modified : 09-December-2015
// ***********************************************************************
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
namespace Types
{
#region " Commandline Parameter (Of T) "
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Represents a Commandline Parameter.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <typeparam name="T">
/// The type of value that the parameter takes.
/// </typeparam>
/// ----------------------------------------------------------------------------------------------------
public class CommandlineParameter<T>
{
#region " Properties "
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets a value indicating whether this parameter is required for the application.
/// <para></para>
/// A value of <see langword="true"/> means the user needs to assign a value for this parameter.
/// <para></para>
/// A value of <see langword="false"/> means this is an optional parameter so no matter if the user sets a custom value.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <value>
/// <see langword="true"/> if this parameter is required for the application; otherwise, <see langword="false"/>.
/// </value>
/// ----------------------------------------------------------------------------------------------------
public bool IsRequired { get; set; }
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the parameter name.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <value>
/// The parameter name.
/// </value>
/// ----------------------------------------------------------------------------------------------------
public string Name {
[DebuggerStepThrough()]
get { return this.nameB; }
[DebuggerStepThrough()]
set { this.EvaluateName(value); }
}
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// ( Backing Field )
/// The parameter name.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
private string nameB;
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the parameter separator.
/// <para></para>
/// This character separates the parameter from the value in the argument.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <value>
/// The parameter separator.
/// </value>
/// ----------------------------------------------------------------------------------------------------
public char Separator {
[DebuggerStepThrough()]
get { return this.separatorB; }
[DebuggerStepThrough()]
set { this.EvaluateSeparator(value); }
}
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// ( Backing Field )
/// The parameter separator.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
private char separatorB;
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the parameter value.
/// <para></para>
/// This value should be initially <see langword="Nothing"/> before parsing the commandline arguments of the application;
/// <para></para>
/// the value of the parameter should be assigned by the end-user when passing an argument to the application.
/// <para></para>
/// To set a default value for this parameter, use <see cref="CommandlineParameter(Of T).DefaultValue"/> property instead.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <value>
/// The parameter value.
/// </value>
/// ----------------------------------------------------------------------------------------------------
public T Value { get; set; }
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Gets or sets the default parameter value.
/// <para></para>
/// This value should be take into account if, after parsing the commandline arguments of the application,
/// <see cref="CommandlineParameter(Of T).Value"/> is <see langword="Nothing"/>,
/// meaning that the end-user didn't assigned any custom value to this parameter.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <value>
/// The default parameter value.
/// </value>
/// ----------------------------------------------------------------------------------------------------
public T DefaultValue { get; set; }
#endregion
#region " Operator Overloading "
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Performs an implicit conversion from <see cref="CommandlineParameter(Of T)"/> to <see cref="CommandlineParameter"/>.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <param name="param">
/// The <see cref="CommandlineParameter(Of T)"/> to convert.
/// </param>
/// ----------------------------------------------------------------------------------------------------
/// <returns>
/// The result of the conversion.
/// </returns>
/// ----------------------------------------------------------------------------------------------------
public static implicit operator CommandlineParameter(CommandlineParameter<T> param)
{
return new CommandlineParameter
{ Name = param.Name,
Separator = param.Separator,
DefaultValue = param.DefaultValue,
Value = param.Value,
IsRequired = param.IsRequired
};
}
#endregion
#region " Private Methods "
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Evaluates an attempt to assign the parameter name.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <param name="name">
/// The parameter name.
/// </param>
/// ----------------------------------------------------------------------------------------------------
/// <exception cref="ArgumentException">
/// The parameter name cannot contain the separator character.;name
/// </exception>
/// ----------------------------------------------------------------------------------------------------
protected virtual void EvaluateName(string name)
{
if (!(this.separatorB.Equals(null)) && (name.Contains(this.separatorB))) {
throw new ArgumentException
(message
: "The parameter name cannot contain the separator character.", paramName
: "name");
} else {
this.nameB = name;
}
}
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Evaluates an attempt to assign the parameter separator.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <param name="separator">
/// The parameter separator.
/// </param>
/// ----------------------------------------------------------------------------------------------------
/// <exception cref="ArgumentException">
/// The parameter separator cannot be any character contained in the parameter name.;separator
/// </exception>
/// ----------------------------------------------------------------------------------------------------
protected virtual void EvaluateSeparator(char separator)
{
if (!(string.IsNullOrEmpty(this.nameB)) && (this.nameB.Contains(separator))) {
throw new ArgumentException
(message
: "The parameter separator cannot be any character contained in the parameter name.", paramName
: "separator");
} else {
this.separatorB = separator;
}
}
#endregion
}
#endregion
#region " Commandline Parameter (Of Object) "
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Represents a Commandline Parameter.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
public sealed class CommandlineParameter : CommandlineParameter<object>
{}
#endregion
}
//=======================================================
//Service provided by Telerik (www.telerik.com)
//=======================================================
Una vez tenemos la base de como representar de forma abstracta un parámetro commandline, creamos las instancias de nuestro Type para crear los parámetros.
( En este ejemplo creo un parámetro que toma como valor una cadena de texto, otro parámetro que toma como valor un booleano, y un array con dichos parámetros. )
private readonly CommandlineParameter
<string> param1
= new CommandlineParameter
<string> { Name = "/Switch1",
Separator = '=',
DefaultValue = "Hello World",
IsRequired = true
};
private readonly CommandlineParameter
<bool> param2
= new CommandlineParameter
<bool> { Name = "/Switch2",
Separator = '=',
DefaultValue = false,
IsRequired = false
};
CommandlineParameter[] @params = {
param1,
param2
};
El modo en que se usuaría la aplicación sería el siguiente:
Aplicación.exe /switch1="valor1" /switch2="true" o "false"
(el orden de los parámetros no importa, y las comillas dobles tampoco, excepto para encerrar strings con espacios en blanco)
Ahora nos faltaría desarrollar el algoritmo de un método que tome como argumento (pasando por referencia) la representación de los parámetros para asignarles los valores y (pasando por valor) los argumentos commandline de la aplicación.
Nota: Adicionalmente le añadí dos parámetros que toman delegados para notificar cuando se encuentra un error de sintaxis o un parámetro requerido que no ha sido encontrado en los argumentos.
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Loop through all the command-line arguments of this application.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <param name="params">
/// The commandline parameters.
/// </param>
///
/// <param name="callbackSyntaxError">
/// An encapsulated method that is invoked if a syntax error is found in one of the arguments.
/// </param>
///
/// <param name="callbackMissingRequired">
/// An encapsulated method that is invoked if a required parameter is missing in the arguments.
/// </param>
/// ----------------------------------------------------------------------------------------------------
private void ParseArguments(ref CommandlineParameter[] @params, Action<CommandlineParameter> callbackSyntaxError, Action<CommandlineParameter> callbackMissingRequired) {
ParseArguments(ref @params, Environment.GetCommandLineArgs.Skip(1), callbackSyntaxError, callbackMissingRequired);
}
/// ----------------------------------------------------------------------------------------------------
/// <summary>
/// Loop through all the command-line arguments of this application.
/// </summary>
/// ----------------------------------------------------------------------------------------------------
/// <param name="params">
/// The commandline parameters.
/// </param>
///
/// <param name="args">
/// The collection of commandline arguments to examine.
/// </param>
///
/// <param name="callbackSyntaxError">
/// An encapsulated method that is invoked if a syntax error is found in one of the arguments.
/// </param>
///
/// <param name="callbackMissingRequired">
/// An encapsulated method that is invoked if a required parameter is missing in the arguments.
/// </param>
/// ----------------------------------------------------------------------------------------------------
private void ParseArguments(ref CommandlineParameter[] @params, IEnumerable<string> args, Action<CommandlineParameter> callbackSyntaxError, Action<CommandlineParameter> callbackMissingRequired) {
List<CommandlineParameter> paramRequired = (from param in @paramswhere param.IsRequired).ToList;
foreach (string arg in args) {
foreach (CommandlineParameter param in @params) {
if (arg.StartsWith(param.Name, StringComparison.OrdinalIgnoreCase)) {
if (!arg.Contains(param.Separator)) {
callbackSyntaxError.Invoke(param);
return;
} else {
string value = arg.Substring(arg.IndexOf(param.Separator) + 1);
if ((paramRequired.Contains(param))) {
paramRequired.Remove(param);
}
if (string.IsNullOrEmpty(value)) {
param.Value = param.DefaultValue;
continue;
} else {
try {
param.Value = Convert.ChangeType(value, param.DefaultValue.GetType());
continue;
} catch (Exception ex) {
callbackSyntaxError.Invoke(param);
return;
}
}
}
}
}
}
if ((paramRequired.Any)) {
callbackMissingRequired.Invoke(paramRequired.First);
}
}
//=======================================================
//Service provided by Telerik (www.telerik.com)
//=======================================================
Por último, así es como usariamos dicho método:
static void Main(string[] args) {
ParseArguments(ref @params, args, Parameter_OnSyntaxError, Parameter_OnMissingRequired)
// Llegados a este punto todo fue exitoso. Continuar con la lógica...
}
private void Parameter_OnSyntaxError(CommandlineParameter param) {
Console.WriteLine(string.Format("[X] Syntax error in parameter: {0}", param.Name));
Environment.Exit(exitCode: 1);
}
private void Parameter_OnMissingRequired(CommandlineParameter param) {
Console.WriteLine(string.Format("[X] Parameter required: {0}", param.Name));
Environment.Exit(exitCode: 1);
}
Nota: La declaración del array
params está en el código de arriba.
Una imagen:
No se si este tocho que he escrito será demasiado tedioso (demasiado código para vagos xD) pero ahí lo dejo para quien le sirva la idea.
Saludos!