Last active
December 6, 2019 18:45
-
-
Save ZacharyPatten/ee71dff90054c8729dc7b93e326dc4da to your computer and use it in GitHub Desktop.
A tool that helps you handle command line arguments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Diagnostics; | |
| using System.Reflection; | |
| using System.Text; | |
| using Towel; | |
| using static Towel.Syntax; | |
| namespace ConsoleCoreSandbox | |
| { | |
| class Program | |
| { | |
| // This code is a snippet from the https://github.com/ZacharyPatten/Towel project. | |
| // Please check out the project if you want to see more code like it. :) | |
| // This is some code that aims to help you handle command line arguments to Console | |
| // applications. Just add "CommandLine.Argument" fields to the class of your entry | |
| // point and they will be automatically resolved. | |
| // | |
| // The "HasValue" property returns true if the argument was | |
| // provided or a default value exists. | |
| // | |
| // The "Status" property gives you information about the argument. | |
| // - Default: The arguemnt was not provided but a default value exists. | |
| // - SyntaxError: The definition of the CommandLineArgument is invalid (not supported). The code needs to be updated. | |
| // - NotProvided: The argument was not provided by the command line arguments and no default exists. | |
| // - DuplicateProvided: The argument was provided multiple times. The command line arguments are invalid. | |
| // - ParseFailed: The argument was provided, but the relative value was not valid. | |
| // - ValueProvided: The argument was provided and successfully parsed. | |
| // | |
| // Examples Of Command Lines: | |
| // dotnet THISAPP A: helloworld | |
| // dotnet THISAPP B: 3 | |
| // dotnet THISAPP A: hello B: 4 C: 11.5 D: world | |
| // dotnet THISAPP Help | |
| // dotnet THISAPP Version | |
| static CommandLine.Argument Version; | |
| static CommandLine.Argument Help; | |
| static CommandLine.Argument<string> A = "default"; | |
| static CommandLine.Argument<int> B = 7; | |
| static CommandLine.Argument<float> C = 8.5f; | |
| static CommandLine.Argument<string> D; | |
| static void Main() | |
| { | |
| if (Version.Exists || Help.Exists) | |
| { | |
| Console.WriteLine(CommandLine.GetDefaultInfoString); | |
| Console.WriteLine(" Summary: TODO"); | |
| Console.WriteLine(" Documentation: TODO"); | |
| Console.WriteLine(" Contact(s): TODO"); | |
| return; | |
| } | |
| Console.WriteLine(A); | |
| Console.WriteLine(B + 1); | |
| Console.WriteLine(C + 1.5); | |
| Console.WriteLine(D.HasValue ? D.Value : nameof(D) + ": " + D.Status); | |
| } | |
| } | |
| } | |
| #region Towel Definitions | |
| namespace Towel | |
| { | |
| public static class CommandLine | |
| { | |
| internal static string[] Args = Environment.GetCommandLineArgs(); | |
| public static string GetDefaultInfoString | |
| { | |
| get | |
| { | |
| StringBuilder stringBuilder = new StringBuilder(); | |
| Assembly entryAssembly = Assembly.GetEntryAssembly(); | |
| AssemblyName assemblyName = entryAssembly.GetName(); | |
| stringBuilder.Append(" Name: "); | |
| stringBuilder.AppendLine(assemblyName.Name); | |
| stringBuilder.Append(" Version: "); | |
| stringBuilder.AppendLine(assemblyName.Version.ToString()); | |
| stringBuilder.Append(" Command Line Arguments:"); | |
| MethodInfo entryMethod = entryAssembly.EntryPoint; | |
| Type entryType = entryMethod.DeclaringType; | |
| FieldInfo[] fieldInfos = entryType.GetFields( | |
| BindingFlags.Static | | |
| BindingFlags.Public | | |
| BindingFlags.NonPublic); | |
| bool hasCommandLineArguments = false; | |
| for (int i = 0; i < fieldInfos.Length; i++) | |
| { | |
| FieldInfo field = fieldInfos[i]; | |
| object fieldValue = field.GetValue(null); | |
| if (fieldValue is IGenericArgument genericArgument) | |
| { | |
| hasCommandLineArguments = true; | |
| stringBuilder.AppendLine(); | |
| stringBuilder.Append(" "); | |
| stringBuilder.Append(field.Name); | |
| stringBuilder.AppendLine(":"); | |
| stringBuilder.Append(" Type: "); | |
| stringBuilder.Append(genericArgument.Type.Name); | |
| if (genericArgument.HasDefaultValue) | |
| { | |
| stringBuilder.AppendLine(); | |
| stringBuilder.Append(" Default: "); | |
| stringBuilder.Append(genericArgument.DefaultValueString); | |
| } | |
| } | |
| else if (fieldValue is Argument) | |
| { | |
| hasCommandLineArguments = true; | |
| stringBuilder.AppendLine(); | |
| stringBuilder.Append(" "); | |
| stringBuilder.Append(field.Name); | |
| } | |
| } | |
| if (!hasCommandLineArguments) | |
| { | |
| stringBuilder.Append(" None"); | |
| } | |
| string result = stringBuilder.ToString(); | |
| return result; | |
| } | |
| } | |
| public enum ArgumentStatus | |
| { | |
| Null = 0, | |
| Default, | |
| SyntaxError, | |
| NotProvided, | |
| DuplicateProvided, | |
| ParseFailed, | |
| ValueProvided, | |
| } | |
| public struct Argument | |
| { | |
| internal class Data | |
| { | |
| internal ArgumentStatus _status; | |
| } | |
| internal Data _data; | |
| public bool Exists | |
| { | |
| get | |
| { | |
| Process(); | |
| return _data._status is ArgumentStatus.ValueProvided; | |
| } | |
| } | |
| public ArgumentStatus Status | |
| { | |
| get | |
| { | |
| Process(); | |
| return _data._status; | |
| } | |
| } | |
| internal void Process() | |
| { | |
| _data ??= new Data(); | |
| if (!(_data._status is ArgumentStatus.Null)) | |
| { | |
| return; | |
| } | |
| Assembly entryAssembly = Assembly.GetEntryAssembly(); | |
| MethodInfo entryMethod = entryAssembly.EntryPoint; | |
| Type entryType = entryMethod.DeclaringType; | |
| FieldInfo[] fieldInfos = entryType.GetFields( | |
| BindingFlags.Static | | |
| BindingFlags.Public | | |
| BindingFlags.NonPublic); | |
| foreach (FieldInfo field in fieldInfos) | |
| { | |
| if (field.GetValue(null) is Argument argumentT) | |
| { | |
| if (argumentT._data is null || !(argumentT._data._status is ArgumentStatus.Null)) | |
| { | |
| continue; | |
| } | |
| if (argumentT._data == _data) | |
| { | |
| string name = field.Name; | |
| int index = -1; | |
| for (int i = 0; i < Args.Length; i++) | |
| { | |
| if (Args[i] == name) | |
| { | |
| if (index > -1) | |
| { | |
| _data._status = ArgumentStatus.DuplicateProvided; | |
| return; | |
| } | |
| else | |
| { | |
| index = i; | |
| } | |
| } | |
| } | |
| if (index == -1) | |
| { | |
| _data._status = ArgumentStatus.NotProvided; | |
| return; | |
| } | |
| else | |
| { | |
| _data._status = ArgumentStatus.ValueProvided; | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| _data._status = ArgumentStatus.SyntaxError; | |
| return; | |
| } | |
| } | |
| internal interface IGenericArgument | |
| { | |
| bool HasDefaultValue { get; } | |
| string DefaultValueString { get; } | |
| Type Type { get; } | |
| } | |
| public struct Argument<T> : IGenericArgument | |
| { | |
| internal class Data | |
| { | |
| internal ArgumentStatus _status; | |
| internal T _value; | |
| internal T _defaultValue; | |
| internal bool _hasDefault; | |
| } | |
| internal Data _data; | |
| public T Value | |
| { | |
| get | |
| { | |
| Process(); | |
| return _data._status is ArgumentStatus.ValueProvided || _data._status is ArgumentStatus.Default | |
| ? _data._value | |
| : throw new InvalidOperationException("Attempted to get a command line argument with a status of " + _data._status + "."); | |
| } | |
| } | |
| public bool HasDefaultValue | |
| { | |
| get | |
| { | |
| Process(); | |
| return _data._hasDefault; | |
| } | |
| } | |
| public T DefaultValue | |
| { | |
| get | |
| { | |
| Process(); | |
| return _data._hasDefault | |
| ? _data._defaultValue | |
| : throw new InvalidOperationException("Attempted to get the default value of a command line argument with no default value."); | |
| } | |
| } | |
| public string DefaultValueString | |
| { | |
| get | |
| { | |
| Process(); | |
| return _data._hasDefault | |
| ? _data._defaultValue.ToString() | |
| : throw new InvalidOperationException("Attempted to get the default value string of a command line argument with no default value."); | |
| } | |
| } | |
| public bool HasValue | |
| { | |
| get | |
| { | |
| Process(); | |
| return _data._status is ArgumentStatus.ValueProvided || _data._status is ArgumentStatus.Default; | |
| } | |
| } | |
| public ArgumentStatus Status | |
| { | |
| get | |
| { | |
| Process(); | |
| return _data._status; | |
| } | |
| } | |
| public Type Type => typeof(T); | |
| internal void Process() | |
| { | |
| _data ??= new Data(); | |
| if (!(_data._status is ArgumentStatus.Null)) | |
| { | |
| return; | |
| } | |
| Assembly entryAssembly = Assembly.GetEntryAssembly(); | |
| MethodInfo entryMethod = entryAssembly.EntryPoint; | |
| Type entryType = entryMethod.DeclaringType; | |
| FieldInfo[] fieldInfos = entryType.GetFields( | |
| BindingFlags.Static | | |
| BindingFlags.Public | | |
| BindingFlags.NonPublic); | |
| foreach (FieldInfo field in fieldInfos) | |
| { | |
| if (field.GetValue(null) is Argument<T> argumentT) | |
| { | |
| if (argumentT._data is null || !(argumentT._data._status is ArgumentStatus.Null)) | |
| { | |
| continue; | |
| } | |
| if (argumentT._data == _data) | |
| { | |
| string name = field.Name + ":"; | |
| int index = -1; | |
| for (int i = 0; i < Args.Length; i++) | |
| { | |
| if (Args[i] == name) | |
| { | |
| if (index > -1) | |
| { | |
| _data._status = ArgumentStatus.DuplicateProvided; | |
| return; | |
| } | |
| else | |
| { | |
| index = i; | |
| } | |
| } | |
| } | |
| if (index == -1) | |
| { | |
| if (_data._hasDefault) | |
| { | |
| _data._status = ArgumentStatus.Default; | |
| return; | |
| } | |
| else | |
| { | |
| _data._status = ArgumentStatus.NotProvided; | |
| return; | |
| } | |
| } | |
| else if (index == Args.Length - 1) | |
| { | |
| _data._status = ArgumentStatus.NotProvided; | |
| return; | |
| } | |
| else if (typeof(T) == typeof(string)) | |
| { | |
| Argument<string>.Data data_string = _data as Argument<string>.Data; | |
| data_string._value = Args[index + 1]; | |
| _data._status = ArgumentStatus.ValueProvided; | |
| return; | |
| } | |
| else if (TryParse(Args[index + 1], out _data._value)) | |
| { | |
| _data._status = ArgumentStatus.ValueProvided; | |
| return; | |
| } | |
| else | |
| { | |
| _data._status = ArgumentStatus.ParseFailed; | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| _data._status = ArgumentStatus.SyntaxError; | |
| return; | |
| } | |
| public static implicit operator T(Argument<T> argument) => argument.Value; | |
| public static implicit operator Argument<T>(T value) => | |
| new Argument<T>() | |
| { | |
| _data = new Data() | |
| { | |
| _defaultValue = value, | |
| _hasDefault = true, | |
| _value = value, | |
| } | |
| }; | |
| } | |
| } | |
| public static class Meta | |
| { | |
| public static MethodInfo GetTryParseMethod<A>() => GetTryParseMethodCache<A>.Value; | |
| public static MethodInfo GetTryParseMethod(Type a) | |
| { | |
| _ = a ?? throw new ArgumentNullException(nameof(a)); | |
| MethodInfo methodInfo = a.GetMethod("TryParse", | |
| BindingFlags.Static | | |
| BindingFlags.Public | | |
| BindingFlags.NonPublic, | |
| null, | |
| new Type[] { typeof(string), a.MakeByRefType() }, | |
| null); | |
| return !(methodInfo is null) | |
| && methodInfo.ReturnType == typeof(bool) | |
| ? methodInfo | |
| : null; | |
| } | |
| internal static class GetTryParseMethodCache<A> | |
| { | |
| internal static readonly MethodInfo Value = GetTryParseMethod(typeof(A)); | |
| } | |
| } | |
| public static class Syntax | |
| { | |
| public static A TryParse<A>(string @string, A Default = default) => | |
| TryParse(@string, out A value) | |
| ? value | |
| : Default; | |
| public static bool TryParse<A>(string @string, out A value) => | |
| TryParseImplementation<A>.Function(@string, out value); | |
| internal static class TryParseImplementation<A> | |
| { | |
| internal static TryParse<A> Function = (string @string, out A value) => | |
| { | |
| static bool Default(string @string, out A value) | |
| { | |
| value = default; | |
| return false; | |
| } | |
| MethodInfo methodInfo = Meta.GetTryParseMethod<A>(); | |
| Function = methodInfo is null | |
| ? Default | |
| : (TryParse<A>)methodInfo.CreateDelegate(typeof(TryParse<A>)); | |
| return Function(@string, out value); | |
| }; | |
| } | |
| } | |
| public delegate bool TryParse<T>(string @string, out T value); | |
| } | |
| #endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment