/* Two types of CLI arguments: --------------------------- 1. Full arguments - listed explicitly (eg. "--arg1") - usually a full word - can contain spaces represented as "--project-name" 2. Shortened arguments - listed implicitly version (eg. "-a") - usually a single letter, interpreted as such Two types of CLI values: ------------------------ 1. String inputs a) Single character (char) b) Single word (Word type) c) Multiple words (Sentence type) 2. Number inputs a) Single digit (Digit type) b) Digit string (Number type) */ using Builtins; namespace CommandLine { /// /// A struct representing a command-line argument. /// /// This contains two properties, a name and a list of aliases /// that can be used instead of the name, and serves as a way /// to represent a command-line argument in a centralised manner. /// public struct Argument { private readonly string _name; private readonly string[] _aliases; public string Name { get { return _name; } } public string[] Aliases { get { return _aliases; } } /// /// Create an Argument instance with only a name and no aliases. /// /// public Argument(string name) { _name = name; _aliases = []; } /// /// Create an Argument instance with a name and an array of aliases. /// /// These aliases will be used when comparing between two arguments /// and serves as a way to tell if arguments are repeated or not. /// /// /// public Argument(string name, string[] aliases) { _name = name; _aliases = aliases; } /// /// Create an Argument instance with a name and a single alias. /// /// Thhis alias will be used when comparing between two arguments /// and serves as a way to tell if arguments are repeated or not. /// /// /// public Argument(string name, string alias) { _name = name; _aliases = [alias]; } public override bool Equals(object? obj) { if (obj is null) return false; if (obj is string) return Name == obj.ToString(); Argument other = (Argument)obj; return Name == other.Name || other.Aliases.Contains(Name) || Aliases.Contains(other.Name); } public static implicit operator string(Argument arg) => arg.Name; public static implicit operator Argument(string str) => new(str); public override int GetHashCode() => Name.GetHashCode() ^ Aliases.GetHashCode(); public override string ToString() => $"Argument({_name}{(_aliases.Length > 0 ? $", aliases = [{string.Join(", ", _aliases)}]" : "")})"; } public abstract class Command { private readonly string _name; private readonly Argument[] _arguments; private readonly string? _doc; public string Name { get { return _name; } } public Argument[] Arguments { get { return _arguments; } } public string? Docstring { get { return _doc; } } public Argument[] ReturnedArguments { get; set; } public static List CreatedCommands { get { List names = []; foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in asm.GetTypes()) { if (type.BaseType == typeof(Command)) { var inst = Activator.CreateInstance(type); if (inst == null) throw new ArgumentNullException("Null error when searching for commands."); names.Add((Command)inst); } } } return names; } } public Command(string name) { _name = name; _arguments = [name]; _doc = null; ReturnedArguments = []; } public Command(string name, string docstring) { _name = name; _arguments = [name]; _doc = docstring.Trim().Replace("\n", "").Replace("\n\n", "\n"); ReturnedArguments = []; } public Command(string name, Argument[] arguments) { if (name == "help") throw new Exception("command names cannot be called 'help'."); _name = name; _arguments = [name, .. arguments]; _doc = null; ReturnedArguments = []; } public Command(string name, Argument[] arguments, string docstring) { if (name == "help") throw new Exception("command names cannot be called 'help'."); _name = name; _arguments = [name, .. arguments]; _doc = docstring.Trim().Replace("\n", "").Replace("\n\n", "\n"); ReturnedArguments = []; } public virtual void Callback() { throw new NotImplementedException("this method must be overrided in a subclass."); } } public struct CLI { public Command[] ValidCommands { get; set; } public CLI(Command[] validCommands) { ValidCommands = validCommands; } public Dictionary ArgParser(string[] argsFromCLI, Argument[] validArguments) { if (validArguments .Where(x => x.Name == "help") .ToList() .Count == 1 && validArguments.Length > 1) { validArguments = [new Argument("help_args")]; } List validArgIndexes = Enumerable .Range(0, argsFromCLI.Length) .Where(n => validArguments.Contains(argsFromCLI[n])) .ToList(); validArgIndexes.Add(argsFromCLI.Length); Dictionary convertedArgs = []; for (int x = 0; x < validArgIndexes.Count - 1; x++) { int firstIndex = validArgIndexes[x]; int secondIndex = validArgIndexes[x + 1]; string first = argsFromCLI[firstIndex]; string other = string.Join(" ", argsFromCLI[(firstIndex + 1) .. secondIndex]); convertedArgs.Add(first, other); } return convertedArgs; } public Command? SingleArgParser(string[] argsFromCLI) { string[] validArguments = ValidCommands.Select(x => x.Name).ToArray(); List validArgIndexes = Enumerable .Range(0, argsFromCLI.Length) .Where(n => validArguments.Contains(argsFromCLI[n])) .ToList(); if (validArgIndexes.Count != 2) return null; int argIndex = validArgIndexes[0]; string other = string.Join(' ', argsFromCLI[(argIndex + 1) .. (argsFromCLI.Length - argIndex)]); Command command = ValidCommands.Where(x => x.Name == argsFromCLI[argIndex]).First(); command.ReturnedArguments = [other]; return command; } } }