1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
/*
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
{
/// <summary>
/// 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.
/// </summary>
public struct Argument
{
private readonly string _name;
private readonly string[] _aliases;
public string Name { get { return _name; } }
public string[] Aliases { get { return _aliases; } }
/// <summary>
/// Create an Argument instance with only a name and no aliases.
/// </summary>
/// <param name="name"></param>
public Argument(string name)
{
_name = name;
_aliases = [];
}
/// <summary>
/// Create an Argument instance with a name and an array of aliases.<para/>
///
/// These aliases will be used when comparing between two arguments
/// and serves as a way to tell if arguments are repeated or not.
/// </summary>
/// <param name="name"></param>
/// <param name="aliases"></param>
public Argument(string name, string[] aliases)
{
_name = name;
_aliases = aliases;
}
/// <summary>
/// Create an Argument instance with a name and a single alias.<para/>
///
/// Thhis alias will be used when comparing between two arguments
/// and serves as a way to tell if arguments are repeated or not.
/// </summary>
/// <param name="name"></param>
/// <param name="alias"></param>
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<Command> CreatedCommands { get {
List<Command> 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<string, string> ArgParser(string[] argsFromCLI, Argument[] validArguments)
{
if (validArguments
.Where(x => x.Name == "help")
.ToList()
.Count == 1
&& validArguments.Length > 1)
{
validArguments = [new Argument("help_args")];
}
List<int> validArgIndexes = Enumerable
.Range(0, argsFromCLI.Length)
.Where(n => validArguments.Contains(argsFromCLI[n]))
.ToList();
validArgIndexes.Add(argsFromCLI.Length);
Dictionary<string, string> 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<int> 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;
}
}
}
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
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<Command> 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)}");
}
}
}
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
using CommandLine;
using Builtins;
class Program
{
static void RunCLI()
{
Console.Title = "Example CLI";
string? input;
// Dictionary<string, string>? 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);
}
}