# MystBin ! - CLI.cs
/*
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;
}
}
}
# MystBin ! - Builtins.cs
using CommandLine;
namespace Builtins
{
// Echo command
class EchoCommand : Command
{
public EchoCommand()
: base("echo", docstring:
"""
A simple echo command that repeats back whatever the user sends.
If the first argument is a number, it will be treated as the number of times
to repeat the following sequence of characters, joined by a space.
Usage
-----
>>> echo hello world
hello world
>>> echo 3 hi
hi hi hi
"""
)
{
}
public override void Callback()
{
string echoString = ReturnedArguments[0];
string[] echoStringParts = echoString.Split(' ');
if (echoStringParts.Length == 1)
{
Console.WriteLine(echoString);
return;
}
string firstEchoWord = echoStringParts[0];
string restOfEchoWord = string.Join(" ", echoStringParts[1 .. echoStringParts.Length]);
if (int.TryParse(firstEchoWord, out int count))
{
Console.WriteLine(string.Join(" ", Enumerable.Range(0, count + 1).Select(_ => restOfEchoWord)));
}
else
{
Console.WriteLine(echoString);
}
}
}
// Close command
class CloseCommand : Command
{
public CloseCommand()
: base("close", docstring:
"""
A simple close command that stops the process entirely and closes the window.
Usage
-----
>>> close
""")
{
}
public override void Callback()
{
Environment.Exit(0);
}
}
// Help command
class HelpCommand : Command
{
public HelpCommand()
: base("help")
{
}
public override void Callback()
{
// Get what the user queried with the help command
string query = ReturnedArguments[0];
// Get all the commands subclassing command
List search = CreatedCommands.Where(cmd => cmd.Name == query).ToList();
if (search.Count == 0)
{
Console.WriteLine($"The search for command '{query}' yielded no results.");
return;
}
Command command = search[0];
string title = $"Help on command '{command}':";
Console.WriteLine($"{title}\n{new string('-', title.Length)}\n{command.Docstring}\n{new string('-', title.Length)}");
}
}
}
# MystBin ! - Program.cs
using CommandLine;
using Builtins;
class Program
{
static void RunCLI()
{
Console.Title = "Example CLI";
string? input;
// Dictionary? args;
Command? command;
HelpCommand help = new();
EchoCommand echo = new();
CloseCommand close = new();
while (true)
{
Console.Write(">>> ");
input = Console.ReadLine();
if (input == null)
{
Console.WriteLine();
continue;
}
CLI cli = new([help, echo, close]);
command = cli.SingleArgParser(input.Split(' '));
if (command == null)
{
Console.WriteLine("Error occured when attempting to parse console input.");
continue;
}
command.Callback();
}
}
static void Main()
{
// RunCLI();
Console.WriteLine(new EchoCommand().Docstring);
}
}