// There is no license for this code. You are free to modify and redistribute it. Also, you can delete this comment. // Original source code: https://github.com/wertrain/command-line-parser-cs (Version 0.2) using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Reflection; namespace CommandLineParser { /// /// Option attribute /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class Option : Attribute { public Option() { } public Option(string longName) => LongName = longName; public Option(char shortName) => ShortName = shortName; public Option(char shortName, string longName) { ShortName = shortName; LongName = longName; } /// long name command option public string LongName { get; private set; } = string.Empty; /// short name command option public char ShortName { get; private set; } /// command option public string HelpText { get; set; } = string.Empty; /// command option public bool Required { get; set; } } /// /// Results type of parse /// public enum ParserResultType { Parsed = 0, NotParsed } /// /// Results of parse /// /// public class ParserResult { internal ParserResult(ParserResultType tag, T value) { Tag = tag; Value = value; Message = string.Empty; } internal ParserResult(ParserResultType tag, T value, string message) { Tag = tag; Value = value; Message = message; } public ParserResultType Tag { get; } public T Value { get; } public string Message { get; } } /// /// Utility for parser /// static class ParserUtility { /// /// Convert string to object using TypeConverter with nullable support /// public static object ConvertToType(string input, Type targetType) { if (targetType == typeof(string)) return input; var nonNullableType = Nullable.GetUnderlyingType(targetType) ?? targetType; if (nonNullableType == typeof(bool)) { if (string.IsNullOrEmpty(input)) return true; // flag only => true if (bool.TryParse(input, out var b)) return b; // 1/0 を解釈する if (input.Equals("1")) return true; if (input.Equals("0")) return false; throw new FormatException($"'{input}' cannot be converted to Boolean."); } var converter = TypeDescriptor.GetConverter(nonNullableType); if (converter != null && converter.CanConvertFrom(typeof(string))) { var result = converter.ConvertFromString(input); if (result == null) { throw new FormatException($"'{input}' cannot be converted to {nonNullableType.Name}."); } return result; } // fallback return Convert.ChangeType(input, nonNullableType); } /// /// Set value to a property on the target instance based on the incoming token text. /// Returns true if a value token was consumed (for non-bool) or successfully set. /// public static bool SetValueToProperty(PropertyInfo property, T target, string? param) { var propertyType = property.PropertyType; var underlyingType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; var isBool = underlyingType == typeof(bool); // Bool: if no explicit value or next token is another option, set true. if (isBool) { if (string.IsNullOrEmpty(param)) { property.SetValue(target, true); return false; // no next token consumed } // explicit true/false if (bool.TryParse(param, out var b)) { property.SetValue(target, b); return true; // consumed param } // If the next token looks like an option (starts with '-' or '--'), treat this as flag only. if (IsOptionToken(param)) { property.SetValue(target, true); return false; } throw new FormatException($"'{param}' cannot be converted to Boolean."); } // Non-bool types require a value. If missing or next is option, it's an error. if (string.IsNullOrEmpty(param) || IsOptionToken(param)) { throw new FormatException($"Option '{property.Name}' requires a value."); } var converted = ConvertToType(param!, propertyType); property.SetValue(target, converted); return true; } public static bool IsOptionToken(string? s) { if (s == null || string.IsNullOrEmpty(s) || s == "-") return false; return s.StartsWith("--", StringComparison.Ordinal) || (s.StartsWith("-", StringComparison.Ordinal) && s.Length > 1 && !char.IsDigit(s[1])); } } /// /// Arguments parser /// public class Parser { private const string HelpLongName = "help"; /// /// Parse from a single string (whitespace split) /// public static ParserResult Parse(string args) where T : new() { var enumerable = string.IsNullOrWhiteSpace(args) ? Array.Empty() : args.Split((string[]?)null, StringSplitOptions.RemoveEmptyEntries); return Parse(enumerable); } /// /// Parse from enumerable of tokens /// public static ParserResult Parse(IEnumerable args) where T : new() { var tokens = (args ?? Enumerable.Empty()).ToArray(); if (tokens.Length == 0) { ShowHelp(); return new ParserResult(ParserResultType.NotParsed, new T()); } var value = new T(); var resultTag = ParserResultType.Parsed; // Prepare property maps only once var props = typeof(T) .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(p => new { Prop = p, Opt = p.GetCustomAttribute