using System.Globalization;
namespace MxGateway.Client.Cli;
/// Parses command-line arguments into flags and named values.
internal sealed class CliArguments
{
private readonly Dictionary _values = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet _flags = new(StringComparer.OrdinalIgnoreCase);
/// Initializes a new instance by parsing the given command-line arguments.
/// Unparsed command-line arguments; flags prefixed with '--' and values follow their flag.
public CliArguments(IEnumerable args)
{
string? pendingName = null;
foreach (string arg in args)
{
if (arg.StartsWith("--", StringComparison.Ordinal))
{
if (pendingName is not null)
{
_flags.Add(pendingName);
}
pendingName = arg[2..];
continue;
}
if (pendingName is null)
{
throw new ArgumentException($"Unexpected argument '{arg}'.");
}
_values[pendingName] = arg;
pendingName = null;
}
if (pendingName is not null)
{
_flags.Add(pendingName);
}
}
/// Returns whether the named flag was present in the arguments.
/// The flag name (without '--' prefix).
public bool HasFlag(string name)
{
return _flags.Contains(name);
}
/// Returns the value for a named argument, or null if absent.
/// The argument name (without '--' prefix).
public string? GetOptional(string name)
{
return _values.TryGetValue(name, out string? value)
? value
: null;
}
/// Returns the value for a required named argument, or throws if absent.
/// The argument name (without '--' prefix).
public string GetRequired(string name)
{
string? value = GetOptional(name);
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException($"Missing required option --{name}.");
}
return value;
}
/// Parses and returns an int32 argument, or the default value if absent.
/// The argument name (without '--' prefix).
/// The default value if the argument is absent; if null, the argument is required.
public int GetInt32(string name, int? defaultValue = null)
{
string? value = GetOptional(name);
if (string.IsNullOrWhiteSpace(value))
{
if (defaultValue.HasValue)
{
return defaultValue.Value;
}
throw new ArgumentException($"Missing required option --{name}.");
}
return int.Parse(value, CultureInfo.InvariantCulture);
}
/// Parses and returns a uint32 argument, or the default value if absent.
/// The argument name (without '--' prefix).
/// The default value if the argument is absent.
public uint GetUInt32(string name, uint defaultValue)
{
string? value = GetOptional(name);
return string.IsNullOrWhiteSpace(value)
? defaultValue
: uint.Parse(value, CultureInfo.InvariantCulture);
}
/// Parses and returns a uint64 argument, or the default value if absent.
/// The argument name (without '--' prefix).
/// The default value if the argument is absent.
public ulong GetUInt64(string name, ulong defaultValue)
{
string? value = GetOptional(name);
return string.IsNullOrWhiteSpace(value)
? defaultValue
: ulong.Parse(value, CultureInfo.InvariantCulture);
}
/// Parses and returns a TimeSpan argument, or the default value if absent. Supports "ms", "s", and standard TimeSpan format.
/// The argument name (without '--' prefix).
/// The default value if the argument is absent.
public TimeSpan GetDuration(string name, TimeSpan defaultValue)
{
string? value = GetOptional(name);
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
if (value.EndsWith("ms", StringComparison.OrdinalIgnoreCase))
{
return TimeSpan.FromMilliseconds(double.Parse(value[..^2], CultureInfo.InvariantCulture));
}
if (value.EndsWith('s'))
{
return TimeSpan.FromSeconds(double.Parse(value[..^1], CultureInfo.InvariantCulture));
}
return TimeSpan.Parse(value, CultureInfo.InvariantCulture);
}
}