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); } }